Lire les erreurs — le guide transversal
Chaque leçon a sa section « lire les messages d'erreur » pour SA notion. Ce fichier est le guide général : l'anatomie d'un traceback, le catalogue des 10 erreurs du débutant, les bugs silencieux, et comment demander de l'aide. À lire une première fois au niveau 1, puis à consulter à chaque erreur incomprise — c'est un fichier de référence, pas une leçon à mémoriser.
Rappel de la méthode (docs/methode.md section 10) : une erreur se traite en 4 étapes — lire le traceback EN ENTIER, la reproduire volontairement dans un fichier minimal, écrire la règle dans ses notes, se créer un exercice. Interdiction de modifier le code au hasard avant l'étape 1.
1. Anatomie complète d'un traceback
Un traceback se lit de bas en haut. La dernière ligne dit QUOI, les lignes au-dessus disent OÙ (et par quel chemin d'appels on y est arrivé).
Exemple réel avec appels imbriqués. Le fichier :
# billing.py
def compute_price(quantity, unit_price):
return quantity * unit_price
def compute_total(order):
price = compute_price(order["quantity"], order["unit_price"])
return price + order["shipping"]
order = {"quantity": "3", "unit_price": 10, "shipping": 5}
print(compute_total(order))
L'exécution produit :
Traceback (most recent call last):
File "C:\Users\jtron\Claude\learnpython\billing.py", line 10, in <module>
print(compute_total(order))
~~~~~~~~~~~~~^^^^^^^
File "C:\Users\jtron\Claude\learnpython\billing.py", line 6, in compute_total
price = compute_price(order["quantity"], order["unit_price"])
File "C:\Users\jtron\Claude\learnpython\billing.py", line 3, in compute_price
return quantity * unit_price
TypeError: can't multiply sequence by non-int of type 'int'
Lecture, de bas en haut :
- Dernière ligne — le diagnostic.
TypeError: can't multiply sequence by non-int of type 'int'. Deux informations : le TYPE d'exception (TypeError— un problème de types) et le message (on essaie de multiplier une « sequence » — ici une chaîne — par quelque chose). C'est la ligne la plus importante du traceback. - La ligne juste au-dessus — le lieu du crash.
line 3, in compute_price, avec la ligne de code affichée :return quantity * unit_price. C'est LÀ que Python a échoué. Mais attention : le lieu du crash n'est pas toujours le lieu de la faute. - En remontant — le chemin d'appels.
compute_total(ligne 6) a appelécompute_price;<module>(ligne 10, c'est-à-dire le fichier lui-même, hors de toute fonction) a appelécompute_total. Chaque étage est un appel de fonction. On remonte la pile jusqu'à trouver l'étage où la donnée fautive est née : ici, ligne 9,"quantity": "3"— une chaîne au lieu d'un entier. La faute est ligne 9, le crash ligne 3. - Première ligne — le cadre.
Traceback (most recent call last)signifie « l'appel le plus récent est en bas ». C'est pour ça qu'on lit de bas en haut. - Les
~~~^^^(Python 3.11+) soulignent l'expression exacte qui a échoué dans la ligne — un vrai cadeau, regarde-les.
Réflexe à automatiser : dernière ligne (quoi) → ligne du crash (où) → remonter la pile (d'où vient la donnée). Dans un traceback long qui traverse des bibliothèques, cherche en remontant la PREMIÈRE ligne qui mentionne TON fichier : c'est presque toujours là que ça se joue.
2. SyntaxError vs erreurs d'exécution : avant / pendant
Deux familles d'erreurs, deux moments différents :
SyntaxError(etIndentationError) : AVANT l'exécution. Python lit tout le fichier avant d'exécuter quoi que ce soit. Si la grammaire est invalide, il refuse de démarrer : aucune ligne n'a été exécutée, même pas la première. C'est pour ça qu'unprint("debut")en ligne 1 ne s'affiche pas quand il y a une SyntaxError en ligne 40 — indice classique qui perturbe les débutants.- Toutes les autres (
NameError,TypeError,ValueError…) : PENDANT l'exécution. Le fichier est grammaticalement correct, le programme démarre, et il crashe en atteignant la ligne fautive. Tout ce qui précède a bien été exécuté (lesprintd'avant se sont affichés, les fichiers d'avant sont écrits).
Conséquence pratique : une SyntaxError se cherche dans le TEXTE du code (souvent à la ligne indiquée ou juste avant — une parenthèse jamais fermée en ligne 12 est signalée en ligne 13) ; une erreur d'exécution se cherche dans les DONNÉES et le chemin qui y mène (le traceback).
3. Catalogue — les 10 erreurs les plus fréquentes du débutant
Pour chacune : le code minimal qui la provoque, le message exact, la cause, la correction. Tape ces exemples au moins une fois (méthode, étape 2 : savoir provoquer une erreur exprès, c'est la comprendre).
3.1 NameError — le nom n'existe pas
message = "hello"
print(mesage)
NameError: name 'mesage' is not defined. Did you mean: 'message'?
Cause : tu utilises un nom que Python ne connaît pas — faute de frappe (90 % des cas), variable utilisée avant sa création, ou définie dans une autre portée (scope, ⏩ niveau 4). Correction : comparer caractère par caractère le nom utilisé et le nom défini. Python 3.10+ suggère souvent le bon nom (Did you mean) — lis-le.
3.2 TypeError — types incompatibles
age = 25
print("Age: " + age)
TypeError: can only concatenate str (not "int") to str
Cause : une opération entre deux types qui ne vont pas ensemble. Ici + entre str et int. Autres classiques : appeler un non-appelable ("hello"()), mauvais nombre d'arguments à une fonction. Correction : convertir explicitement ("Age: " + str(age)) ou, mieux, f-string : print(f"Age: {age}"). Face à un TypeError, demande-toi : quel est le type RÉEL de chaque opérande ? (print(type(x)) en cas de doute.)
3.3 ValueError — bon type, mauvaise valeur
answer = input("Your age? ") # user types: twenty
age = int(answer)
ValueError: invalid literal for int() with base 10: 'twenty'
Cause : l'opération accepte le type (une str, d'accord) mais pas cette valeur-là ("twenty" n'est pas convertible en entier). Quasi toujours autour des conversions et de input(). Correction : valider avant de convertir, ou encadrer de try/except (⏩ niveau 5) :
try:
age = int(answer)
except ValueError:
print("Please enter a number.")
3.4 IndexError — indice hors de la liste
scores = [10, 20, 30]
print(scores[3])
IndexError: list index out of range
Cause : les indices vont de 0 à len(liste) - 1. Une liste de 3 éléments a les indices 0, 1, 2 — pas 3. Classique aussi : liste vide, ou boucle avec range(len(scores) + 1). Correction : dernier élément = scores[-1] ou scores[len(scores) - 1]. Vérifier len() avant d'indexer, ou mieux : itérer directement (for score in scores:) au lieu d'indexer.
3.5 KeyError — clé absente du dictionnaire
user = {"name": "Lea", "age": 30}
print(user["email"])
KeyError: 'email'
Cause : la clé demandée n'existe pas dans le dict. Souvent une faute de frappe sur la clé, ou une donnée optionnelle jamais renseignée. Correction : user.get("email") renvoie None (ou une valeur par défaut : user.get("email", "unknown")) au lieu de crasher ; ou tester if "email" in user:. Pour déboguer : print(user.keys()) montre les clés réellement présentes.
3.6 AttributeError — l'objet n'a pas cet attribut/méthode
name = "Lea"
name.append("!")
AttributeError: 'str' object has no attribute 'append'
Cause : tu appelles une méthode qui n'existe pas pour CE type. append existe pour les listes, pas pour les chaînes. Variante très fréquente : 'NoneType' object has no attribute ... → ta variable vaut None, souvent parce qu'une fonction n'a rien retourné (voir section 4.3) ou parce que tu as écrit ma_liste = ma_liste.sort() (sort() renvoie None). Correction : vérifier le type réel (print(type(name))) puis chercher la bonne méthode (dir(name) les liste toutes ; pour une str : name + "!").
3.7 IndentationError — indentation incohérente
if score > 10:
print("won")
IndentationError: expected an indented block after 'if' statement on line 1
Cause : en Python l'indentation EST la structure. Après un : il faut un bloc indenté ; et dans un même bloc, toutes les lignes doivent avoir la même indentation. Variante sournoise : mélange de tabulations et d'espaces (TabError: inconsistent use of tabs and spaces in indentation). Correction : indenter de 4 espaces après chaque :. Régler VS Code pour insérer des espaces (c'est le défaut) et ne jamais mélanger. Détectée AVANT l'exécution, comme SyntaxError.
3.8 SyntaxError — grammaire invalide
if score > 10
print("won")
SyntaxError: expected ':'
Cause : le code viole la grammaire de Python — : oublié, parenthèse ou guillemet jamais fermé, = (affectation) au lieu de == (comparaison) dans une condition. Rien ne s'exécute (section 2). Correction : lire la ligne indiquée ET la ligne d'avant (une parenthèse ouverte ligne 12 fait crier Python ligne 13). Les messages de Python 3.10+ sont devenus très précis (expected ':', '(' was never closed) — les lire littéralement.
3.9 ZeroDivisionError — division par zéro
total = 100
count = 0
average = total / count
ZeroDivisionError: division by zero
Cause : le dénominateur vaut 0 au moment de la division. Rarement écrit en dur : presque toujours une variable qui SE RETROUVE à 0 (liste vide dont on fait la moyenne, compteur jamais incrémenté). Correction : traiter le cas zéro AVANT de diviser :
average = total / count if count > 0 else 0
et se demander pourquoi count vaut 0 — c'est souvent le vrai bug.
3.10 FileNotFoundError — fichier introuvable
with open("data.txt") as f:
content = f.read()
FileNotFoundError: [Errno 2] No such file or directory: 'data.txt'
Cause : le chemin est relatif au répertoire courant du terminal, pas au fichier .py. Si tu lances python lessons/level-05/script.py depuis la racine du repo, "data.txt" est cherché à la racine, pas à côté du script. Autres causes : faute de frappe, extension cachée par Windows (data.txt.txt). Correction : afficher où Python se croit (import os; print(os.getcwd())), utiliser un chemin construit à partir du script (pathlib, ⏩ niveau 5), ou en écriture laisser open("out.txt", "w") créer le fichier.
4. Les bugs SANS message d'erreur (les pires)
Un traceback est un cadeau : il dit où regarder. Un bug de logique, lui, ne dit rien — le programme tourne et produit un résultat faux. Les quatre classiques :
4.1 La boucle infinie
i = 0
while i < 5:
print("working...")
# forgot: i = i + 1
Le programme ne s'arrête jamais (Ctrl+C pour tuer). Cause : la condition du while ne devient jamais fausse — rien dans le corps de la boucle ne la fait avancer. Règle : à chaque while, se demander « quelle ligne du corps rapproche la condition de False ? ». Pas de réponse = boucle infinie.
4.2 La condition toujours vraie (ou toujours fausse)
answer = input("Continue? (y/n) ")
if answer == "y" or "Y": # BUG: always True
print("continuing")
Se lit « y ou Y » mais Python lit (answer == "y") or ("Y") — et "Y", chaîne non vide, est toujours vraie. Le if passe TOUJOURS. Correction : if answer == "y" or answer == "Y": ou if answer.lower() == "y":. Même famille : = voulu == (SyntaxError dans un if, mais bug silencieux ailleurs), et les bornes décalées (> voulu >=).
4.3 L'oubli de return
def compute_total(prices):
total = sum(prices)
# forgot: return total
result = compute_total([10, 20])
print(result) # None
print(result + 5) # TypeError much later
La fonction calcule bien, mais sans return elle renvoie None. Le bug n'explose pas ici : il explose PLUS LOIN (un TypeError ou AttributeError: 'NoneType'... à 30 lignes de la vraie faute). Règle : quand une valeur vaut None sans raison, chercher la fonction qui l'a produite et vérifier son return. Et : print dans la fonction n'est PAS un return.
4.4 La f-string sans f
name = "Lea"
print("Hello {name}") # Hello {name}
Aucune erreur : Python affiche littéralement Hello {name}, accolades comprises. Il manque juste le f : print(f"Hello {name}"). Ça se voit à l'œil dans la sortie — encore faut-il LIRE sa sortie au lieu de la survoler.
Comment débusquer un bug silencieux
- Prédiction de sortie : écrire sur papier ce que le programme DOIT afficher, ligne par ligne, puis exécuter et comparer. La première ligne qui diffère borne le bug : il est avant elle.
- Print de débogage : encadrer la zone suspecte de prints qui montrent les valeurs ET leur position :
print(f"DEBUG before loop: i={i}, total={total}")
Puis resserrer : moitié du programme, quart, ligne. C'est une dichotomie. Retirer les prints DEBUG une fois le bug trouvé (ou les committer, honte éternelle — git status te sauvera).
- Exécution pas à pas dans Python Tutor : tu VOIS chaque variable changer à chaque ligne. Imbattable pour les boucles et la mutabilité (voir
docs/ressources.md). - La méthode du canard : expliquer le code à voix haute, ligne par ligne, à un objet inanimé. Le bug se révèle souvent au moment où ce que tu DIS ne correspond plus à ce qui est ÉCRIT.
5. Demander de l'aide intelligemment (à Claude ou à un humain)
« Ça marche pas » est indéboguable. Le format question minimale, à utiliser systématiquement — il tient en 5 points :
- Ce que je veux : le comportement attendu, en une phrase. « Je veux que le programme redemande tant que la saisie n'est pas un nombre. »
- Ce que j'obtiens : le comportement réel, factuel. « Il crashe à la première saisie non numérique. »
- Le code minimal reproductible : PAS ton fichier de 120 lignes — le plus petit extrait qui reproduit le problème (souvent 5–15 lignes). Construire cet extrait résout d'ailleurs le problème une fois sur trois à lui seul : en enlevant du code, tu trouves la ligne qui change tout.
- Le traceback complet : copié-collé en entier, de
Traceback (most recent call last)à la dernière ligne. Jamais de paraphrase (« ça me met une erreur de type ») : le message exact contient la moitié du diagnostic. - Ce que j'ai déjà essayé : deux ou trois pistes tentées et leur résultat. Ça évite qu'on te resserve ce qui a échoué, et ça prouve (à toi d'abord) que tu as cherché.
Ce format vaut pour Claude comme pour un forum ou un collègue. Avec Claude, une règle de plus, imposée par la méthode : demander des indices d'abord, la solution ensuite seulement. « Donne-moi un indice sur ce qui cloche, pas la correction » — les prompts prêts à l'emploi sont dans PROMPTS.md. Une solution reçue sans avoir cherché est une occasion d'apprendre détruite.
6. Comment chercher
Dans l'ordre :
- Le message d'erreur lui-même. Relis-le littéralement. Les messages de Python moderne (3.10+) disent souvent la correction en clair (
Did you mean: 'message'?,expected ':'). La moitié des recherches Google sont des messages non lus. - La doc officielle avant Google : docs.python.org (version française, partielle — voir
docs/ressources.md). Pour une méthode : la page du type (str,list,dict) ; pour une exception : la page « Built-in Exceptions ». La doc dit ce qui est VRAI pour ta version de Python ; un blog de 2019 ou une réponse StackOverflow votée en 2015, pas forcément. Apprendre à lire la doc EST une compétence du parcours — chaque heure investie rapporte pendant des années. - Google/StackOverflow ensuite, avec le message exact entre guillemets, moins les parties propres à toi (chemins, noms de variables) :
"TypeError: can only concatenate str (not \"int\") to str". - Claude enfin, au format question minimale (section 5), indices d'abord.
Pourquoi copier-coller une solution sans la comprendre est interdit par la méthode (README, principe 1 ; docs/methode.md) : le but du parcours n'est pas que CE programme marche, c'est que TOI tu saches écrire le suivant. Une solution collée sans être comprise fait passer le symptôme et laisse la cause — la lacune — intacte ; l'erreur reviendra, dans un contexte où le copier-coller ne s'appliquera plus, et tu seras exactement aussi démuni qu'aujourd'hui plus une mauvaise habitude. La règle pratique : tu peux t'inspirer de n'importe quelle solution trouvée, à condition de (1) pouvoir l'expliquer ligne par ligne à voix haute, (2) la retaper de mémoire dans ton fichier (pas Ctrl+V), (3) noter dans tes notes la règle qu'elle t'a apprise. Si tu ne peux pas faire le (1), tu n'as pas trouvé une solution — tu as trouvé du texte.