Lire un traceback et gérer les exceptions utiles en Python

Lire un traceback et gérer les exceptions Python : une loupe repère la ligne en erreur, puis une flèche mène vers un bloc corrigé.

Pour comprendre les exceptions Python, commencez par lire la dernière ligne du diagnostic, puis repérez l’instruction qui a échoué et les valeurs qu’elle utilise. Le traceback, ou trace de la pile d’appels, indique la succession des appels qui ont conduit à l’échec. Il vous aide à décider s’il faut corriger le programme ou traiter une situation prévue, comme une saisie impossible à convertir.

Vous allez provoquer une erreur de type, lire sa sortie, rétablir un calcul et utiliser try, except, else, finally et raise. Le but est de produire un résultat vérifiable tout en laissant visibles les erreurs qui demandent une correction.

Erreur, exception et traceback : trois repères

Une exception signale une situation qui interrompt le déroulement normal d’un bloc de code. Lorsqu’elle n’est pas prise en charge, Python affiche généralement son type, son message d’erreur et une trace des appels (traceback). Dans un notebook, l’exécution de la cellule concernée s’arrête ; le noyau peut encore exécuter d’autres cellules. Les variables déjà définies ne sont pas effacées pour autant.

Une erreur de syntaxe empêche Python de comprendre le code, par exemple si les deux-points d’un if manquent. Elle est signalée par SyntaxError, qui appartient aussi à la hiérarchie des exceptions. Le try présent dans cette cellule ne peut pas intercepter cette erreur de syntaxe, car le code doit être analysé avant d’être exécuté. À l’inverse, une addition de types incompatibles peut être syntaxiquement correcte et échouer pendant l’exécution. La documentation Python sur les erreurs et exceptions présente cette distinction.

Lire un traceback à partir d’un calcul qui échoue

Les valeurs 23, 19 et 24 proviennent de la colonne Hours_Studied du classeur StudentPerformanceFactors.xlsx. Cette colonne indique le nombre d’heures d’étude par semaine.

Pour observer un échec, écrivons volontairement "19" entre guillemets. Cette modification crée une chaîne de caractères dans la liste.

heures_etude = [23, "19", 24]
total_heures = sum(heures_etude)
print("Total :", total_heures)
TypeError                                 Traceback (most recent call last)
Cell In[2], line 2
      1 heures_etude = [23, "19", 24]
----> 2 total_heures = sum(heures_etude)
      3 print("Total :", total_heures)

TypeError: unsupported operand type(s) for +: 'int' and 'str'

La sortie ci-dessus provient de l’exécution du notebook. Le message anglais reste celui de Python. Voici comment le lire :

  1. Commencez par la dernière ligne. TypeError désigne une opération appliquée à un type incompatible. Le message indique ici que l’addition rencontre un entier, int, et une chaîne, str.
  2. Repérez la ligne exécutée. Cell In[2], line 2 renvoie à la deuxième ligne de la cellule exécutée avec le compteur 2. La flèche pointe vers sum(heures_etude). Dans un script, vous verrez plutôt un nom de fichier et un numéro de ligne.
  3. Examinez les valeurs utilisées. Le problème vient du "19" introduit à la ligne précédente. L’endroit où Python détecte l’échec n’est donc pas nécessairement celui où la donnée incorrecte a été créée.
Diagnostic des exceptions Python : lire le type d’erreur et le message, repérer la ligne exécutée, puis vérifier les valeurs utilisées.
Pour analyser l’échec, reliez le message à la ligne exécutée et aux valeurs utilisées.

Une trace plus longue peut traverser plusieurs fonctions ou des fichiers de bibliothèque. Les appels y apparaissent du plus ancien au plus récent, jusqu’au point d’échec. Repérez également la ligne de votre propre code qui a fourni les entrées. Si plusieurs exceptions s’enchaînent, lisez les messages de liaison entre leurs traces : la dernière exception peut avoir été provoquée lors du traitement d’une précédente. Le module officiel traceback décrit ces informations et leur affichage.

Corriger la cause et recalculer

Rétablissons les trois nombres du classeur, puis recalculons le total et la moyenne dans la même cellule. Cette redéfinition évite de réutiliser un ancien total_heures si l’affectation précédente a échoué.

heures_etude = [23, 19, 24]
total_heures = sum(heures_etude)
print("Total :", total_heures)
print("Moyenne :", total_heures / len(heures_etude))
Total : 66
Moyenne : 22.0

Le total vaut 66 et la moyenne de ces trois observations vaut 22.0 heures par semaine.

Reconnaître les exceptions Python utiles au projet

Le nom de l’exception oriente la vérification. Il ne dicte pas à lui seul la correction : un ValueError peut provenir d’une saisie attendue ou d’une erreur dans la préparation des données. Les définitions ci-dessous s’appuient sur la référence des exceptions natives.

ExceptionCe qu’elle signaleVérification utile
NameErrorUn nom utilisé n’est pas défini dans ce contexte.Vérifiez son orthographe et exécutez la cellule qui le définit.
TypeErrorUne opération reçoit un type incompatible.Examinez les objets, par exemple un nombre et une chaîne dans une somme.
ValueErrorLe type convient, mais la valeur est inacceptable.Vérifiez le format ou la règle de validation.
IndexErrorUn indice est hors des limites d’une séquence.Contrôlez la longueur de la liste et l’indice demandé.
KeyErrorUne clé demandée est absente.Vérifiez les clés du dictionnaire ou les noms des colonnes.
ZeroDivisionErrorUne division utilise un dénominateur nul.Avant une moyenne, contrôlez que la liste n’est pas vide.
FileNotFoundErrorUn fichier ou dossier demandé est introuvable.Vérifiez le chemin et le dossier de travail.
ModuleNotFoundErrorUn module importé est introuvable.Vérifiez son nom et l’environnement du noyau sélectionné.
Quelques exceptions rencontrées dans un projet de données, et la première vérification utile.

Un nom mal écrit ou une colonne absente demande généralement une correction du code ou du fichier attendu. Une saisie utilisateur mal formée peut, elle, être refusée avec un message compréhensible. Cette distinction détermine où un gestionnaire d’exception sera utile.

Gérer les exceptions Python avec try, except et else

Supposons qu’une durée d’étude arrive sous forme de texte. int("19") renvoie l’entier 19, tandis que int("dix-neuf") lève ValueError. Dans cet exemple, nous attendons des chaînes représentant des nombres entiers ; une chaîne comme "19.5" n’est pas acceptée par int. La référence de int précise la conversion.

for saisie in ["19", "dix-neuf"]:
    try:
        heures = int(saisie)
    except ValueError as erreur:
        print("Saisie refusée :", saisie)
        print("Détail :", erreur)
    else:
        print("Heures acceptées :", heures)
Heures acceptées : 19
Saisie refusée : dix-neuf
Détail : invalid literal for int() with base 10: 'dix-neuf'

try contient la conversion susceptible d’échouer. except ValueError as erreur définit un gestionnaire d’exception : un bloc exécuté pour traiter une exception du type indiqué. Le nom erreur donne accès à l’objet exception et à son message. Le bloc else s’exécute si la conversion réussit. Lorsqu’une exception est interceptée, Python saute les instructions restantes de try, exécute le gestionnaire, puis poursuit après l’ensemble try/except/else.

Si une opération lève TypeError alors que le gestionnaire prévoit seulement except ValueError, ce gestionnaire ne s’exécute pas. Python cherche alors un autre except adapté dans les blocs try qui entourent le code ou dans le code qui a appelé la fonction.

C’est la même exception qui « remonte » ainsi vers un endroit capable de la traiter : on parle de propagation de l’exception. Si aucun gestionnaire adapté n’est trouvé, l’exécution du script ou de la cellule s’arrête avec un traceback ; Python ne reprend pas simplement à la ligne suivante. La documentation Python sur le traitement des exceptions décrit ce mécanisme.

Gardez le try aussi court que possible. L’affichage du cas valide reste dans else, afin qu’un éventuel problème dans cette partie ne soit pas attribué à la conversion. Une exception levée dans else n’est pas interceptée par le except qui le précède. Les règles complètes figurent dans la référence de l’instruction try.

Avant de gérer des exceptions Python, définissez ce que votre programme accepte : ici, des chaînes représentant un nombre entier d’heures. Distinguez le message destiné à l’utilisateur du détail technique conservé pour le diagnostic. Un message comme « Saisissez un nombre entier d’heures » indique quoi corriger ; le type et le message d’origine permettent de rechercher la cause. Une exception inattendue doit rester visible. Élargir un except uniquement pour faire disparaître le traceback rendrait la vérification du programme plus difficile.

Les deux schémas comparent le même traitement. Si try réussit, Python exécute else ; si l’exception est interceptée, il exécute le except correspondant. À gauche, le programme poursuit ensuite son exécution. À droite, il passe d’abord par finally. Ces chemins supposent que les blocs exécutés se terminent normalement.

Deux schémas : sans finally, try mène à else ou à except puis à la suite ; avec finally, ces deux branches exécutent d’abord l’action de fin. Une exception non traitée se propage après finally si ce bloc est présent.
Gestion des exceptions Python : comparaison de try, except et else avec ou sans finally. Les blocs exécutés se terminent ici normalement. Une exception non traitée se propage, après l’action de finally lorsque cette clause est présente.

Quand utiliser finally ? Ajoutez cette clause lorsqu’une ressource doit être libérée en quittant le bloc, même si une erreur survient : fermer une connexion ou libérer un verrou, par exemple. Une simple conversion avec int ne nécessite pas de nettoyage ; try, except et else suffisent alors. Pour un fichier, préférez généralement with, qui assure sa fermeture automatiquement. La documentation Python sur les actions de nettoyage explique le rôle de finally.

Identifier une exception dont le type n’est pas encore connu

Si vous ne connaissez pas encore le type d’erreur rencontré, vous pouvez utiliser temporairement except Exception as erreur pour l’identifier dans ce petit exemple. Exception est une classe de base commune à la plupart des exceptions ordinaires, notamment ValueError et TypeError. Ce gestionnaire accepte donc plusieurs types d’erreurs. Il ne couvre pas toutes les exceptions : KeyboardInterrupt et SystemExit n’en dérivent pas.

Reprenons les mêmes saisies en ajoutant l’affichage du type de l’exception. Le bloc try reste limité à la conversion.

for saisie in ["19", "dix-neuf"]:
    try:
        heures = int(saisie)
    except Exception as erreur:
        print("Saisie refusée :", saisie)
        print("Type :", type(erreur).__name__)
        print("Détail :", erreur)
    else:
        print("Heures acceptées :", heures)
Heures acceptées : 19
Saisie refusée : dix-neuf
Type : ValueError
Détail : invalid literal for int() with base 10: 'dix-neuf'

as erreur donne accès à l’objet exception. type(erreur) renvoie sa classe, puis .__name__ fournit le nom de cette classe : ici, ValueError. L’affichage de erreur donne le message qui précise la cause.

Avec "19", la conversion réussit et else affiche la valeur acceptée. Avec "dix-neuf", la conversion échoue : le gestionnaire affiche la saisie refusée, le type ValueError et son message.

Une fois le type identifié, préférez except ValueError as erreur pour traiter précisément l’erreur attendue, comme dans l’exemple précédent. Garder except Exception pourrait aussi intercepter une erreur de programmation que vous n’aviez pas prévu de traiter. Cette forme générale sert ici au diagnostic ; elle ne doit pas masquer une erreur inattendue.

Signaler une valeur invalide et comprendre finally

Lever une exception avec raise

Une conversion réussie ne suffit pas à valider le sens d’une entrée. La valeur -1 est bien un entier, mais une durée d’étude ne peut pas être négative. raise permet de signaler explicitement ce refus avec un message précis.

heures = -1
if heures < 0:
    raise ValueError("Hours_Studied doit être positif ou nul.")
print("Heures acceptées :", heures)
ValueError                                Traceback (most recent call last)
Cell In[6], line 3
      1 heures = -1
      2 if heures < 0:
----> 3     raise ValueError("Hours_Studied doit être positif ou nul.")
      4 print("Heures acceptées :", heures)

ValueError: Hours_Studied doit être positif ou nul.

Cette deuxième erreur est volontaire. Le message indique le champ concerné et la règle vérifiée : la durée doit être positive ou nulle. Reprenons avec 19, une valeur de notre extrait :

heures = 19
if heures < 0:
    raise ValueError("Hours_Studied doit être positif ou nul.")
print("Heures acceptées :", heures)
Heures acceptées : 19

Exécuter une action de fin avec finally

La clause finally sert à exécuter une action avant de quitter l’instruction try, qu’une exception ait été traitée ou qu’elle doive se propager. L’exemple suivant permet de suivre cet ordre d’exécution :

for saisie in ["19", "dix-neuf"]:
    try:
        heures = int(saisie)
    except ValueError:
        print("Conversion impossible.")
    else:
        print("Heures acceptées :", heures)
    finally:
        print("Fin de la vérification.")
Heures acceptées : 19
Fin de la vérification.
Conversion impossible.
Fin de la vérification.

Le message de fin apparaît pour les deux saisies. Il ne signifie pas que la conversion a réussi. Ici, cet affichage sert à observer l’ordre d’exécution. N’utilisez pas un return dans finally pour étouffer une erreur : il peut remplacer le résultat ou empêcher la propagation d’une exception.

Si le bloc finally se termine normalement, une exception non prévue qui survient dans try continue ensuite de se propager. Le programme ne reprend donc pas automatiquement après cette instruction. Dans la gestion des exceptions Python, afficher un message de fin ne signifie ni « donnée valide » ni « erreur traitée ». Le schéma ci-dessous suppose que l’action de fin s’achève normalement, sans modifier le chemin de sortie.

Exceptions Python : après une conversion réussie ou une exception non traitée, finally s’exécute avant la suite ou la propagation.
finally exécute l’action de fin avant la suite normale ou la propagation de l’exception.

Exercice : refuser une saisie sans fausser la moyenne

Les trois valeurs de l’extrait arrivent maintenant sous forme de texte : "23", "19" et "24". Ajoutez "à vérifier" pour tester une entrée invalide. Écrivez un traitement qui convertit les chaînes et refuse les durées négatives. Il doit afficher la moyenne uniquement si toutes les entrées sont valides et si la liste contient au moins une valeur.

Vérifiez trois cas : la liste avec l’entrée invalide, les trois chaînes valides et une liste vide. Une saisie refusée ne doit ni devenir zéro ni disparaître silencieusement du calcul.

Afficher la correction et les sorties vérifiées
for saisies in [
    ["23", "19", "24", "à vérifier"],
    ["23", "19", "24"],
    [],
]:
    heures_valides = []
    saisies_refusees = []
    for saisie in saisies:
        try:
            heures = int(saisie)
            if heures < 0:
                raise ValueError("Durée négative.")
        except ValueError:
            saisies_refusees.append(saisie)
        else:
            heures_valides.append(heures)

    if saisies_refusees:
        print("Calcul suspendu. Saisies refusées :", saisies_refusees)
    elif not heures_valides:
        print("Calcul suspendu. Aucune valeur.")
    else:
        moyenne = sum(heures_valides) / len(heures_valides)
        print("Moyenne des heures étudiées :", moyenne)
Calcul suspendu. Saisies refusées : ['à vérifier']
Moyenne des heures étudiées : 22.0
Calcul suspendu. Aucune valeur.

Le premier cas suspend le calcul et conserve la saisie à revoir. Le deuxième produit 22.0. Le troisième évite la division par zéro en signalant l’absence de valeurs. La liste saisies_refusees permet de signaler les entrées à corriger avant de calculer la moyenne.

Les entrées prévues ici sont des chaînes de caractères. Si un autre composant transmet None à la place, la conversion lève TypeError ; ce cas inattendu reste visible. Pour prendre en charge ce cas, il faudrait préciser les types de valeurs acceptés et le traitement prévu pour chacun.

Quand le programme continue mais reste incorrect

  • Un ancien résultat s’affiche après une erreur. Si le calcul à droite du signe = échoue, l’affectation n’a pas lieu. La variable conserve son ancienne valeur si elle était déjà définie. Redéfinissez les entrées et recalculez dans l’ordre.
  • Une erreur est remplacée par zéro. Le calcul peut alors aboutir, mais la moyenne repose sur une valeur inventée. Refusez l’entrée ou appliquez une règle de traitement des données explicitement justifiée.
  • Le même message apparaît après la correction. Vérifiez que vous avez exécuté la cellule modifiée, puis contrôlez ses dépendances. Le texte enregistré et l’état du noyau sont deux choses différentes.
  • Un fichier ou un module reste introuvable. Un gestionnaire ne crée pas le fichier et n’installe pas le paquet. Corrigez le chemin, le nom ou l’environnement concerné avant de relancer.

À retenir

Un traceback fournit un point de départ pour le diagnostic. Lisez son type et son message, repérez la ligne exécutée, puis contrôlez les entrées. Corrigez les erreurs du programme et réservez les gestionnaires aux situations pour lesquelles une réponse précise est prévue. Après modification, vérifiez aussi le résultat obtenu : l’absence d’exception ne prouve pas qu’un calcul est correct.

Références pour poursuivre