Erreurs et exceptions
Lire un traceback, rattraper une erreur avec try, et déboguer méthodiquement
§1.Deux familles d'erreurs
Les erreurs de SYNTAXE : ton texte n'est pas du Python. Un guillemet manquant, un deux-points oublié. Python refuse d'exécuter quoi que ce soit — même la première ligne. Elles sont pénibles mais faciles : le message pointe l'endroit.
Les erreurs d'EXÉCUTION : le programme est valide, il démarre, et il casse en cours de route. Diviser par zéro, convertir « bonjour » en nombre, lire un index qui n'existe pas. On les appelle des EXCEPTIONS.
Il existe une troisième famille, la pire : les erreurs de LOGIQUE. Le programme tourne sans broncher et donne un résultat faux. Aucun message ne t'avertit — seul un test bien choisi peut les révéler.
Trois informations, toujours les mêmes, à lire du BAS vers le HAUT.
Traceback (most recent call last): File "<stdin>", line 7, in <module> File "<stdin>", line 3, in moyenneZeroDivisionError: divide by zeroLa DERNIÈRE ligne dit ce qui s'est passé : une division par zéro. Juste au-dessus, l'endroit exact : ligne 3, dans la fonction moyenne. Encore au-dessus, qui l'a appelée : la ligne 7. On lit donc de bas en haut — du symptôme vers son origine.
§3.Les exceptions les plus courantes
| Exception | Ce qui s'est passé | Exemple typique |
|---|---|---|
| ZeroDivisionError | Division par zéro | Moyenne d'une liste vide |
| ValueError | Bon type, mauvaise valeur | int("bonjour") |
| TypeError | Mauvais type | "3" + 5 |
| IndexError | Index hors limites | liste[10] sur 3 éléments |
| KeyError | Clé absente d'un dictionnaire | notes["Inconnu"] |
| NameError | Nom inconnu | Faute de frappe sur une variable |
| AttributeError | Méthode inexistante pour ce type | un entier auquel on demande .upper() |
§4.Rattraper une exception : try / except
Une exception non rattrapée arrête le programme. Parfois c'est ce qu'on veut. Mais quand l'erreur est PRÉVISIBLE — un utilisateur qui tape n'importe quoi, un fichier absent — mieux vaut la traiter proprement.
Le bloc try contient le code risqué ; le bloc except dit quoi faire si ça casse. Si rien ne casse, le except est simplement ignoré.
def lire_entier(texte): try: return int(texte) except ValueError: return None print(lire_entier("42"))print(lire_entier("bonjour"))print(lire_entier("3.5"))int("3.5") échoue aussi : ce n'est pas l'écriture d'un ENTIER. La fonction renvoie None dans les deux cas d'échec, laissant l'appelant décider quoi faire — plutôt que d'imposer un message d'erreur.
def diviser(a, b): try: resultat = a / b except ZeroDivisionError: print("division par zéro") return None except TypeError: print("il faut des nombres") return None else: print("calcul réussi") return resultat finally: print("-- terminé --") print(diviser(10, 2))print(diviser(10, 0))else s'exécute si AUCUNE exception n'a eu lieu. finally s'exécute dans TOUS les cas, succès comme échec — c'est là qu'on referme un fichier ou une connexion. Remarque qu'il tourne même après le return.
§9.Lever ses propres exceptions
Quand ta fonction reçoit quelque chose d'incohérent, elle a deux choix : renvoyer une valeur spéciale comme None, ou signaler franchement le problème avec raise.
raise est souvent le meilleur choix. Renvoyer None laisse l'erreur se propager silencieusement et se manifester bien plus loin, à un endroit qui n'a rien à voir. Une exception, elle, s'arrête immédiatement au bon endroit et dit ce qui ne va pas.
class NoteInvalide(Exception): pass def valider(note): if not isinstance(note, (int, float)): raise TypeError("une note doit être un nombre") if note < 0 or note > 20: raise NoteInvalide("note hors barème : " + str(note)) return note for essai in [15, 25, "douze"]: try: print("ok :", valider(essai)) except NoteInvalide as e: print("refusée ->", e) except TypeError as e: print("type ->", e)Une exception personnalisée n'est qu'une classe qui hérite d'Exception — trois mots suffisent. Le as e récupère l'objet exception, dont l'affichage donne le message. Nommer ses erreurs rend le code bien plus lisible que des codes de retour.
§11.Déboguer méthodiquement
Face à un programme qui ne fait pas ce qu'on veut, le réflexe naturel est de modifier au hasard jusqu'à ce que ça marche. C'est le plus mauvais réflexe : on finit par masquer le symptôme sans comprendre la cause, et le bug revient plus tard.
La méthode qui fonctionne est celle du scientifique : formuler une hypothèse précise, la tester, en tirer une conclusion. Chaque test doit éliminer une possibilité.
§12.La démarche, dans l'ordre
- Lis le traceback en entierLe type, la ligne, le message. La réponse y est très souvent.
- Reproduis de façon fiableTrouve le plus petit cas qui déclenche le problème. Un bug qu'on sait reproduire est à moitié résolu.
- Formule une hypothèse« Je pense que ma liste est vide à ce moment-là. » Une phrase vérifiable, pas une impression.
- Vérifie avec un printAffiche la valeur ET son type juste avant la ligne qui casse. Le type révèle la moitié des bugs.
- ConclusHypothèse confirmée : corrige. Infirmée : passe à la suivante. Jamais au hasard.
- Retire les print de débogageOu remplace-les par un vrai test, qui vérifiera automatiquement que le bug ne revient pas.
Afficher la valeur ne suffit pas toujours — c'est souvent le TYPE qui trahit le problème.
notes = ["12", "15", "9"] # lues depuis une saisie print("valeur :", notes[0])print("type :", type(notes[0])) # total = sum(notes) -> TypeError : ce sont des chaînes ! total = sum(int(n) for n in notes)print("total :", total)À l'affichage, « 12 » et 12 sont indiscernables. type() lève le doute immédiatement. C'est le premier réflexe quand un calcul donne un résultat absurde.
Ce programme tourne sans erreur, et pourtant son résultat est faux. Applique la méthode.
À retenir
- Syntaxe : rien ne démarre. Exécution : ça casse en route. Logique : ça tourne et c'est faux — le pire.
- Un traceback se lit du BAS vers le HAUT : le type d'abord, puis l'endroit, puis qui a appelé.
- try / except rattrape une erreur PRÉVISIBLE ; else si tout va bien, finally dans tous les cas.
- N'écris jamais except: tout seul — tu masquerais tes propres fautes de frappe.
- raise signale franchement une donnée incohérente, là où renvoyer None laisse l'erreur se propager.
- Une exception sur mesure tient en deux lignes : class MonErreur(Exception): pass
- Déboguer : hypothèse, print de la valeur ET du type, conclusion. Jamais au hasard.