La clause else attachée à une boucle for en Python reste l’une des constructions les plus mal comprises du langage. Elle n’a rien à voir avec le else d’un if : le bloc else d’un for s’exécute uniquement si la boucle se termine sans rencontrer de break. En production, cette mécanique couvre des situations précises où l’alternative classique (flag booléen) alourdit le code sans raison.
for/else et recherche avec rupture : le pattern que les flags remplacent mal
Le cas canonique en projet pro concerne la recherche d’un élément dans une collection avec sortie anticipée. Un pipeline de validation de commande, par exemple, itère sur une liste de règles métier. Dès qu’une règle bloque la commande, un break interrompt la boucle.
Le bloc else qui suit le for ne s’exécute que si aucune règle n’a déclenché de break, ce qui signifie que la commande est valide. Sans for/else, le même comportement exige une variable temporaire initialisée avant la boucle, testée après. Sur un fichier de plusieurs centaines de lignes, ces flags parasites compliquent la relecture en code review.
Nous observons ce pattern dans les validateurs de formulaires Django, les contrôles de conformité sur des flux de données pandas, et les vérifications de dépendances dans des scripts de déploiement. Le gain n’est pas la performance, c’est la lisibilité du chemin nominal versus le chemin d’erreur.

for/else dans un pipeline de données : validation de fichiers et parsing
Dans les projets data où l’on traite des fichiers CSV ou JSON par lots, un script doit souvent vérifier qu’un enregistrement attendu existe dans un fichier avant de poursuivre le traitement. Le for/else exprime directement cette intention.
Vérification d’un identifiant dans un lot de fichiers
Prenons un ETL qui parcourt les lignes d’un fichier pour trouver un identifiant de référence. Si l’identifiant est trouvé, le script passe au traitement suivant via break. Si la boucle se termine normalement, le else lève une exception ou journalise l’absence.
Ce pattern évite un anti-pattern fréquent : accumuler un booléen found = False, le basculer à True dans la boucle, puis tester found après. Sur un projet avec plusieurs dizaines de ces vérifications, supprimer les flags réduit le bruit dans les diffs Git et facilite le travail des reviewers.
Parsing de formats semi-structurés
Les parseurs de logs ou de fichiers de configuration utilisent for/else quand ils cherchent une section ou une directive précise. Si la directive est absente, le else déclenche un fallback ou une alerte. Nous recommandons ce pattern chaque fois que la logique se résume à « chercher X, réagir si X absent ».
for/else versus match/case depuis Python 3.10 : quand migrer
Depuis Python 3.10, les longues chaînes if/elif/else qui routent des événements ou classifient des messages migrent vers match/case dans les projets maintenus activement. Le for/else, en revanche, n’entre pas en concurrence avec match/case parce qu’il résout un problème différent : la sortie anticipée d’une itération, pas le pattern matching sur une valeur.
La confusion vient du fait que certaines équipes remplacent des boucles for avec elif internes par un match/case sur le type d’élément itéré. C’est pertinent quand chaque élément a un type ou une structure distincte. Mais match/case ne remplace pas la sémantique « aucun break rencontré » du for/else.
En pratique, les deux coexistent :
- match/case pour dispatcher un événement vers le bon handler selon sa structure (routage de messages, parseurs de protocoles)
- for/else pour valider qu’au moins un élément satisfait une condition dans une séquence, avec rupture dès la première correspondance
- if/elif/else classique pour les branchements simples à deux ou trois chemins, où ni match ni for/else n’apportent de clarté supplémentaire

Profondeur d’imbrication et refactoring du for/else en code pro
Le for/else devient un problème quand il s’imbrique. Un for/else dans un for/else dans un try/except produit un code que personne ne relit sans effort. Nous appliquons une règle simple : un seul niveau de for/else par fonction. Au-delà, il faut extraire la boucle interne dans une fonction dédiée qui renvoie un booléen ou lève une exception.
Critères de refactoring que nous utilisons
- Si le bloc else dépasse trois lignes, extraire la logique dans une fonction nommée qui documente l’intention (« handle_missing_record », « raise_if_no_match »)
- Si la boucle for contient elle-même un if/elif avec plus de deux branches, envisager de séparer le filtrage (générateur ou compréhension) de la recherche (any() ou next() avec default)
- Si le for/else apparaît dans une méthode de classe déjà longue, c’est un signal que la méthode fait trop de choses et qu’un découpage s’impose
Le builtin any() avec une expression génératrice remplace souvent un for/break/else quand le bloc else se limite à un test de présence. La forme any(condition for item in iterable) renvoie True ou False sans nécessiter de boucle explicite. Nous réservons for/else aux cas où le break déclenche un traitement spécifique (pas juste un booléen) et où le else gère l’absence avec une logique propre.
Piège classique : else exécuté sur boucle vide
Un point que la majorité des tutoriels survolent : si l’itérable est vide, le for ne s’exécute jamais, et le else s’exécute quand même. En production, cela signifie qu’un for/else sur une queryset Django vide ou une liste filtrée à zéro élément déclenchera le bloc else comme si « aucun élément ne correspondait », alors qu’aucun élément n’a été testé.
La parade consiste à vérifier la longueur de l’itérable avant la boucle, ou à utiliser une garde if not iterable en amont. Ne pas traiter ce cas revient à masquer un bug silencieux dans un pipeline de données, où une table vide peut survenir après un filtrage agressif sans que personne ne s’en aperçoive.
Le for/else reste un outil de niche en Python. Il ne convient ni à toutes les boucles, ni à tous les projets. Mais dans les contextes de validation séquentielle, de recherche avec rupture et de parsing de lots, il produit un code plus court et plus explicite que l’alternative à base de flags, à condition de respecter une discipline stricte sur la profondeur d’imbrication et la gestion des itérables vides.

