For Python else : erreurs fréquentes et corrections pas à pas

Le mot-clé else en Python ne signifie pas toujours « sinon ». Placé après une boucle for ou un bloc try, il change complètement de rôle. Cette ambiguïté provoque des bugs silencieux que ni le message d’erreur ni le traceback ne signalent, parce que le code est syntaxiquement correct.

Le piège du else « post-succès » dans for et try en Python

Vous avez déjà vu un else après un if : il s’exécute quand la condition est fausse. Le réflexe naturel consiste à transposer cette logique partout. Avec une boucle for, ce réflexe mène droit au bug.

Un else rattaché à un for ne signifie pas « si la boucle n’a rien trouvé ». Il signifie : le bloc else s’exécute uniquement si aucun break n’a interrompu la boucle. La nuance est technique, mais ses conséquences sont concrètes.

Prenons un exemple simple. Ce script cherche un nombre négatif dans une liste :

for n in [3, 7, -1, 4]:
  if n < 0:
    print("Trouvé :", n)
    break
else:
  print("Aucun négatif")

Ici, le break est atteint, donc le bloc else ne s’exécute pas. Retirez le -1 de la liste : la boucle se termine normalement, et le else affiche « Aucun négatif ». Jusque-là, tout semble logique.

Le problème arrive quand un développeur oublie le break ou le supprime lors d’un refactoring. Sans break, le bloc else s’exécute systématiquement après la boucle, quel que soit le contenu de la liste. Le code ne plante pas. Aucun traceback. Le script produit un résultat faux sans aucun signal d’alerte.

Développeur comparant des extraits de code Python imprimés pour corriger des erreurs de syntaxe for-else

Else après try except : isoler la ligne risquée du post-traitement

Le même mot-clé else joue un rôle différent dans un bloc try/except. Le code placé dans le else ne s’exécute que si le try n’a levé aucune exception. C’est un vrai « post-succès ».

Pourquoi ne pas mettre ce code directement à la fin du try ? Parce que le else d’un try n’est pas protégé par les except du même bloc. Si une erreur survient dans le else, elle remonte sans être capturée. C’est exactement le comportement souhaité : vous distinguez l’erreur de la ligne risquée d’une erreur dans le traitement qui suit.

Voici la structure recommandée :

try:
  resultat = int(valeur)  # seule ligne risquée
except ValueError:
  print("Conversion impossible")
else:
  print("Résultat :", resultat * 2)  # post-traitement
finally:
  print("Bloc terminé")

Si int(valeur) échoue, le except gère le ValueError. Si la conversion réussit, le else exécute le calcul. Si ce calcul produit lui-même une erreur (par exemple un TypeError inattendu), elle n’est pas avalée par le except ValueError. Elle remonte dans la pile d’appels et reste visible lors du débogage.

Le cas du except nu qui masque tout

Un except sans type d’exception capture tout, y compris les fautes de frappe sur les noms de variables (NameError) et les interruptions clavier (KeyboardInterrupt). Ce pattern rend le débogage quasi impossible dans un script de production.

  • Un except Exception capture les erreurs applicatives sans bloquer les signaux système, ce qui constitue un premier garde-fou raisonnable.
  • Un except ciblé (par exemple except ValueError, KeyError) documente les erreurs attendues et laisse remonter les autres.
  • Isoler la ligne risquée dans le try et déplacer le reste dans else empêche qu’une erreur de post-traitement soit confondue avec l’erreur initiale.

Corrections pas à pas des erreurs fréquentes avec for else

Les bugs liés au for/else partagent un schéma commun : le développeur écrit un else en pensant « sinon » alors que Python lit « pas de break ».

Erreur 1 : else sans break dans la boucle

C’est le cas le plus courant. Le code ressemble à ceci :

for user in users:
  if user.active:
    print(user.name)
else:
  print("Aucun utilisateur actif")

Sans break après le print, le else s’exécute dans tous les cas. Le message « Aucun utilisateur actif » apparaît même quand la liste contient des utilisateurs actifs. La correction consiste à ajouter un break après la ligne print(user.name), ou à remplacer le pattern par une variable booléenne.

Erreur 2 : le else indenté au niveau du if

L’indentation détermine si le else appartient au if ou au for. Un décalage d’un niveau transforme un « post-boucle » en « branche conditionnelle ». Vérifiez toujours à quel bloc votre else est rattaché en contrôlant l’indentation.

Deux développeurs collaborant pour corriger des erreurs dans une boucle for-else Python sur un écran partagé

Erreur 3 : supprimer le break lors d’un refactoring

Quand on simplifie une boucle, le break disparaît parfois. Le else reste en place et devient du code mort qui s’exécute à chaque itération complète. Les linters modernes signalent désormais un else de boucle inutile quand la boucle ne contient pas de break, avec une recommandation de supprimer le else et de dédenter le code.

Quand utiliser (et éviter) le else de boucle en Python

Le for/else a un cas d’usage légitime : les recherches avec sortie anticipée. Vous parcourez une collection, vous cherchez un élément, et le else gère le cas « non trouvé ». En dehors de ce schéma précis, le pattern crée plus de confusion qu’il n’en résout.

  • Utilisez for/else quand la boucle contient un break conditionnel et que vous avez besoin d’un traitement « non trouvé » juste après.
  • Préférez une variable booléenne (found = False) si la logique de recherche est complexe ou imbriquée, car le else de boucle devient illisible avec plusieurs niveaux.
  • Dans un bloc try/except, placez systématiquement le post-traitement dans le else plutôt qu’à la fin du try, pour séparer la gestion d’erreur du traitement nominal.
  • Supprimez tout else de boucle orphelin (sans break correspondant) : c’est du code trompeur.

La règle de lisibilité la plus fiable reste celle-ci : si un collègue doit relire votre else deux fois pour comprendre à quel bloc il appartient, remplacez-le par une structure plus explicite. Un code clair bat toujours un raccourci syntaxique ambigu.

A voir sans faute