Cours de Python / Gérer les exceptions avec try, except, else, finally et raise

La gestion des exceptions distingue les échecs attendus que le programme peut traiter de ceux qu’il doit transmettre à son appelant. Gardez un bloc try ciblé et n’interceptez que les erreurs auxquelles vous savez réagir, afin de ne pas masquer un défaut sans rapport avec la récupération prévue.

Comprendre les chemins de try, except, else et finally

Si une instruction du bloc try lève une exception, le reste de ce bloc est sauté. Python cherche alors un gestionnaire except correspondant au type de l’exception. Une erreur peut aussi provenir d’une fonction appelée dans le try. Si aucun gestionnaire adapté n’existe à ce niveau, elle se propage vers l’appelant.

La clause else s’exécute après un try terminé sans exception dans le déroulement normal. Elle permet de séparer l’action qui peut échouer du travail à effectuer après sa réussite. Une exception déclenchée dans else n’est pas capturée par l’except associé au même try. finally sert à la finalisation lorsque le contrôle quitte cette construction, sur le chemin normal comme sur le chemin d’exception.

raise signale explicitement une règle violée. Dans le programme ci-dessous, la conversion et le refus d’un montant négatif produisent tous deux une ValueError, mais pour des causes différentes. Les règles officielles de gestion des exceptions Python expliquent les gestionnaires, la propagation et la finalisation.

Exécuter les cas de montant accepté ou invalide

Python 3 et sa bibliothèque standard suffisent. Enregistrez le programme complet ci-dessous dans exceptions_demo.py, en UTF-8, dans un dossier d’exercice. Dans un terminal ouvert dans ce dossier avec l’environnement virtuel du projet activé, lancez python exceptions_demo.py. Sur macOS ou Ubuntu hors environnement virtuel, utilisez python3 exceptions_demo.py si votre interpréteur porte ce nom.

def parse_amount(text):
    amount = int(text)
    if amount < 0:
        raise ValueError("amount must be nonnegative")
    return amount

for text in ["8000", "abc", "-1"]:
    try:
        amount = parse_amount(text)
    except ValueError as error:
        print("invalid:", text)
    else:
        print("accepted:", amount)
    finally:
        print("checked")

Le résultat attendu est :

accepted: 8000
checked
invalid: abc
checked
invalid: -1
checked

8000 se convertit en entier non négatif : le else affiche accepted. abc échoue dans int, tandis que -1 se convertit correctement puis échoue sur la règle explicite du montant. Les deux passent par except et affichent invalid. Le finally affiche checked après chacun des trois cas. Ces mots signifient respectivement « accepté », « invalide » et « vérifié ».

Le programme poursuit la boucle après une ValueError traitée. Il ne réutilise pas amount dans le gestionnaire : un appel ayant échoué ne lui a pas nécessairement fourni de nouvelle valeur. Cette séparation évite de continuer un calcul avec un résultat manquant ou provenant d’une itération précédente.

Traiter une cause au niveau où la récupération est possible

Une conversion d’entier peut produire une ValueError ; l’accès à un fichier peut produire une FileNotFoundError ou une PermissionError. Ces situations ne se réparent pas de la même manière. except Exception: pass pourrait laisser le programme continuer sans résultat valide et sans signaler l’échec. Ajoutez seulement le contexte utile et laissez remonter une erreur inattendue lorsque vous ne pouvez pas la traiter correctement.

Intercepter une exception puis renvoyer une valeur depuis une fonction transforme ce chemin en retour normal pour l’appelant. Un raise sans argument dans le gestionnaire relance au contraire l’exception courante. Si vous devez la transformer en une erreur de plus haut niveau, raise RuntimeError("message") from error peut conserver le lien explicite avec sa cause. Choisissez une récupération réelle, pas seulement la disparition du message.

Utilisez autant que possible un gestionnaire de contexte comme with pour les ressources qui le prennent en charge, par exemple dans la leçon sur l’ouverture et la fermeture des fichiers. Un finally écrit à la main doit tenir compte d’une ressource qui n’a pas pu être créée. La finalisation ne garantit ni la réussite du nettoyage lui-même, ni son exécution après un arrêt brutal du processus ou de la machine.

Évitez un return dans finally : il peut remplacer un résultat prévu ou supprimer une exception en cours. De même, une nouvelle exception produite par le nettoyage peut devenir l’erreur visible. Gardez ce bloc concentré sur la finalisation et testez aussi ses chemins d’échec.

Vérifier les chemins normaux, les rejets et la propagation

  1. Dans la liste de chaînes de l’exercice, ajoutez seulement "0". Ce cas doit afficher accepted: 0 puis checked. Le zéro reste un montant autorisé. Retirez ensuite cet ajout.
  2. Ajoutez seulement la chaîne vide "". Sa conversion produit une ValueError ; le programme affiche invalid avec une saisie vide, puis checked. Retirez cet essai.
  3. Pour comparer les causes de abc et -1, ajoutez temporairement print(str(error)) dans le gestionnaire, après l’affichage invalid et avec la même indentation. Le premier message décrit une conversion impossible ; le second vaut amount must be nonnegative, soit « le montant doit être non négatif ». Retirez la ligne pour retrouver les six lignes de référence.
  4. Dans une variante d’essai, ajoutez None à la liste. int(None) produit une TypeError que le gestionnaire ValueError ne capture pas. Le finally affiche encore checked avant que cette erreur non traitée termine le programme. Restaurez la liste initiale.

La variable error sert ici à observer une erreur à l’intérieur de son gestionnaire. Les détails d’une exception peuvent contenir des valeurs d’entrée, des chemins ou d’autres informations qui ne doivent pas être publiées telles quelles. Dans une application, donnez une explication utile à l’utilisateur et limitez les diagnostics aux données nécessaires au dépannage.