Maîtriser for Python else pour éviter les erreurs de logique cachées

La clause else rattachée à une boucle for en Python est l’une des constructions les plus mal comprises du langage. Son bloc ne s’exécute que si la boucle se termine sans rencontrer de break. Toute confusion sur ce mécanisme introduit des erreurs de logique silencieuses qui ne lèvent aucune exception et passent les tests unitaires sans broncher.

Pourquoi for else piège même les développeurs expérimentés

Le mot-clé else après un for n’a pas le même sens que dans un if/else. Il faudrait le lire comme « no break » plutôt que comme « sinon ». Cette asymétrie sémantique provoque un pattern récurrent : le développeur place dans le bloc else du code qu’il croit conditionné à « la boucle n’a rien trouvé », alors que ce bloc s’exécute aussi quand l’itérable est vide.

Prenons un cas concret de recherche dans une liste :

for user in users:
  if user.is_admin:
    grant_access(user)
    break
else:
  deny_all()

Si users est une liste vide, la boucle ne s’exécute pas, aucun break n’est rencontré, et deny_all() est appelé. Le comportement semble correct ici par accident. Changez la logique du else pour une opération de journalisation (« aucun admin trouvé parmi N utilisateurs ») et le bug devient invisible : le message apparaît même quand la liste est vide, faussant les métriques.

Le bloc else d’un for s’exécute toujours si aucun break n’intervient, y compris sur un itérable vide. Nous recommandons de traiter ce cas limite explicitement avec un if not collection avant la boucle.

Programmeuse Python travaillant sur une boucle for-else dans un espace de coworking cosy avec un ordinateur portable ouvert

Séparer chemin de succès et chemin d’erreur avec try except else

Le else du bloc try/except répond à un problème différent mais tout aussi piégeux. Son rôle : exécuter du code uniquement si le try n’a levé aucune exception. Placer cette logique de succès directement dans le try est une erreur de conception fréquente.

Le risque des blocs try trop larges

Un bloc try qui englobe à la fois l’opération risquée et le traitement post-succès masque les erreurs nées de ce traitement. Si une exception survient dans la logique de suivi (mise à jour d’état, écriture de log), elle est capturée par le except comme si elle provenait de l’opération initiale.

Ce pattern est désormais traité comme un finding à part entière dans les revues de code orientées sécurité. Un except générique combiné à un try trop large peut convertir un échec d’authentification en chemin de succès, par exemple quand une erreur de parsing du token est avalée silencieusement.

La correction est structurelle :

try:
  token = parse_token(raw)
except InvalidToken:
  reject_request()
else:
  update_session(token)
  log_success(token.user_id)

Ici, une exception dans update_session ou log_success remonte naturellement au lieu d’être capturée par le except InvalidToken. Le else agit comme un mur entre l’opération risquée et la logique qui suppose sa réussite.

Patterns courants d’erreurs de logique avec for else en Python

Nous observons trois cas récurrents où for...else introduit des bugs silencieux :

  • Itérable vide non vérifié : le bloc else s’exécute alors qu’aucune itération n’a eu lieu, déclenchant une action (notification, écriture en base) sur un état inexistant. Le correctif consiste à tester la vacuité avant la boucle.
  • Break conditionnel mal placé : le break est dans une branche if imbriquée qui n’est jamais atteinte à cause d’une condition trop restrictive. Le else s’exécute systématiquement, donnant l’illusion que la recherche échoue toujours.
  • Confusion sémantique avec if/else : le développeur traite le else du for comme un « sinon pour chaque élément » et y place du code qui devrait être dans un if à l’intérieur de la boucle. Le code compile, les tests passent sur les jeux de données habituels, le bug ne se révèle qu’en production.

Deux ingénieurs logiciels collaborant sur une erreur de logique Python for-else devant un double écran dans un bureau open space

Alternatives au for else en code de production Python

La lisibilité du for...else fait débat depuis des années dans la communauté Python. En code de production, nous recommandons souvent de le remplacer par un pattern explicite qui ne repose pas sur la connaissance de cette syntaxe particulière.

Le pattern avec variable sentinelle

found = False
for item in collection:
  if item.matches(criteria):
    found = True
    break
if not found:
  handle_not_found()

Ce pattern est plus verbeux, mais son intention est lisible par n’importe quel développeur, y compris ceux venant de langages qui n’ont pas de for...else. La variable sentinelle rend le flux de contrôle explicite.

Le pattern avec fonction dédiée et return

Extraire la recherche dans une fonction permet d’utiliser return au lieu de break, ce qui élimine le besoin du else :

def find_admin(users):
  for user in users:
    if user.is_admin:
      return user
  return None

Une fonction avec return early est plus testable qu’un for else car chaque chemin de sortie est explicite et peut être couvert par un test unitaire dédié.

Quand garder for else

Le for...else reste pertinent dans les scripts courts, les prototypes et le code algorithmique où le développeur maîtrise la sémantique. Dans un contexte d’équipe large ou de code review systématique, le coût cognitif de la syntaxe dépasse souvent son bénéfice.

Checklist de revue de code pour les clauses else en Python

Avant de valider un else rattaché à un for ou un try, nous appliquons ces vérifications :

  • L’itérable peut-il être vide ? Si oui, le comportement du else est-il correct dans ce cas ?
  • Le break est-il atteignable dans tous les scénarios prévus, ou une condition trop stricte le rend-il mort ?
  • Dans un try/except/else, la logique post-succès est-elle bien dans le else et non dans le try ?
  • Le except capture-t-il une exception spécifique ou un Exception générique qui masquerait des erreurs de logique ?

Ces quatre points suffisent à attraper la majorité des erreurs de logique cachées liées aux clauses else. Le réflexe le plus rentable reste de se demander, à chaque for...else rencontré : « que se passe-t-il si la boucle ne s’exécute jamais ? »

Articles en vedette