Python & AI Tutorials Logo
Programmation Python

24. Comprendre les erreurs et les tracebacks

Les erreurs font inévitablement partie de la programmation. Chaque programmeur, du débutant à l’expert, en rencontre régulièrement. La différence entre se débattre avec les erreurs et apprendre d’elles réside dans la compréhension de ce que Python essaie de vous dire quand quelque chose tourne mal.

Quand Python rencontre un problème dans votre code, il ne s’arrête pas simplement en silence : il fournit des informations détaillées sur ce qui s’est mal passé, où cela s’est produit, et souvent des indices sur le pourquoi. Apprendre à lire et à interpréter ces messages d’erreur est l’une des compétences les plus précieuses que vous pouvez développer en tant que programmeur.

Dans ce chapitre, nous allons explorer les deux grandes catégories d’erreurs que vous rencontrerez : les erreurs de syntaxe (problèmes dans la façon dont vous avez écrit le code) et les exceptions à l’exécution (problèmes qui se produisent pendant l’exécution du code). Nous apprendrons à lire les tracebacks (traceback) — les rapports d’erreur détaillés de Python — et à comprendre comment les exceptions modifient le flux normal de votre programme. Surtout, nous développerons un état d’esprit de débogage (debugging) qui considère les erreurs non comme des échecs, mais comme des informations précieuses qui vous aident à écrire un meilleur code.

24.1) Erreurs de syntaxe vs exceptions à l’exécution

Python distingue deux types de problèmes fondamentalement différents dans votre code : les erreurs de syntaxe et les exceptions à l’exécution. Comprendre cette distinction vous aide à diagnostiquer les problèmes plus rapidement et à savoir où chercher des solutions.

24.1.1) Ce que sont les erreurs de syntaxe

Une erreur de syntaxe se produit lorsque Python ne peut pas comprendre votre code parce qu’il viole les règles grammaticales du langage. Tout comme « The cat sat on the » est une phrase anglaise incomplète, un code contenant des erreurs de syntaxe est du Python incomplet ou mal formé que l’interpréteur ne peut pas analyser.

Les erreurs de syntaxe sont détectées avant l’exécution de votre programme. Python lit d’abord l’intégralité de votre script, en vérifiant qu’il respecte les règles du langage. S’il trouve une erreur de syntaxe, il refuse d’exécuter le moindre code — même les parties correctes.

Voici un exemple simple :

python
# AVERTISSEMENT : erreur de syntaxe - uniquement pour démonstration
# ERREUR : deux-points manquant après l'instruction if
age = 25
if age >= 18
    print("You are an adult")

Quand vous essayez d’exécuter ce code, Python signale immédiatement :

  File "example.py", line 3
    if age >= 18
                ^
SyntaxError: expected ':'

Remarquez plusieurs caractéristiques clés de ce message d’erreur :

  1. Fichier et numéro de ligne : Python vous indique exactement où il a trouvé le problème (line 3)
  2. Indicateur visuel : le caret (^) pointe l’endroit où Python a été déstabilisé
  3. Type d’erreur : SyntaxError identifie clairement qu’il s’agit d’un problème de grammaire
  4. Indice utile : expected ':' indique ce qu’il manque

Le code ne s’exécute jamais parce que Python ne peut même pas commencer à l’exécuter : la syntaxe est invalide.

Regardons une autre erreur de syntaxe courante :

python
# AVERTISSEMENT : erreur de syntaxe - uniquement pour démonstration
# ERREUR : parenthèses non correspondantes
numbers = [1, 2, 3, 4, 5]
total = sum(numbers
print(f"Total: {total}")

Python signale :

  File "example.py", line 2
    total = sum(numbers
               ^
SyntaxError: '(' was never closed

Ici, Python a détecté que nous avons ouvert une parenthèse à la ligne 2 mais que nous ne l’avons jamais fermée. L’erreur est signalée à la ligne 2 (là où se trouve la parenthèse non fermée), et le caret pointe l’endroit où Python s’attendait à trouver la parenthèse fermante.

Caractéristiques clés des erreurs de syntaxe :

  • Détectées avant l’exécution de tout code
  • Empêchent l’exécution de l’ensemble du programme
  • Indiquent généralement des fautes de frappe, de la ponctuation manquante ou une indentation incorrecte
  • L’emplacement de l’erreur peut être légèrement après la véritable erreur

24.1.2) Ce que sont les exceptions à l’exécution

Une exception à l’exécution (ou simplement « exception ») se produit lorsqu’un code syntaxiquement correct rencontre un problème pendant l’exécution. Le code est du Python grammaticalement valide, mais quelque chose se passe mal lorsque le programme s’exécute réellement.

Contrairement aux erreurs de syntaxe, les exceptions se produisent pendant l’exécution de votre programme. Python a analysé votre code avec succès et a commencé à l’exécuter, mais a ensuite rencontré une situation qu’il ne pouvait pas gérer.

Voici un exemple simple :

python
# Ce code a une syntaxe valide mais va lever une exception
numbers = [10, 20, 30]
print(numbers[0])  # Output: 10
print(numbers[5])  # Cette ligne va lever une IndexError
print("This line never executes")

Output:

10
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(numbers[5])
          ~~~~~~~^^^
IndexError: list index out of range

Voici ce qui s’est passé :

  1. La première instruction print s’est exécutée avec succès (nous avons vu 10)
  2. La deuxième print a tenté d’accéder à l’index 5, qui n’existe pas
  3. Python a levé une exception IndexError
  4. Le programme s’est arrêté, et la troisième print ne s’est jamais exécutée

Le code était syntaxiquement correct : Python n’a eu aucun problème à comprendre ce que nous voulions faire. Le problème est apparu à l’exécution quand nous avons tenté d’accéder à un élément de liste qui n’existait pas.

Voici un autre exemple montrant un autre type d’exception à l’exécution :

python
# Syntaxe valide, mais division par zéro à l'exécution
def calculate_average(total, count):
    return total / count
 
# Celles-ci fonctionnent correctement
print(calculate_average(100, 4))  # Output: 25.0
print(calculate_average(75, 3))   # Output: 25.0
 
# Celle-ci lève une exception
print(calculate_average(50, 0))   # ZeroDivisionError

Output:

25.0
25.0
Traceback (most recent call last):
  File "example.py", line 8, in <module>
    print(calculate_average(50, 0))
          ^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 2, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

La fonction a parfaitement fonctionné deux fois, mais lors du troisième appel, nous avons passé 0 comme count, ce qui a provoqué une division par zéro. Python ne pouvait pas détecter ce problème avant que le code ne s’exécute réellement avec ces valeurs spécifiques.

Caractéristiques clés des exceptions à l’exécution :

  • Se produisent pendant l’exécution du programme
  • Le code est syntaxiquement valide
  • Dépendent souvent de données ou de conditions spécifiques
  • Le programme s’exécute jusqu’au point où l’exception se produit
  • Des entrées différentes peuvent provoquer des exceptions différentes (ou aucune)

24.1.3) Comparer les erreurs de syntaxe et les exceptions à l’exécution

Voyons les deux types d’erreurs côte à côte pour comprendre leurs différences :

python
# Exemple 1 : Erreur de syntaxe
# ERREUR : guillemet fermant manquant
print("Program started!")
message = "Hello, world
print(message)

Cela produit immédiatement une erreur de syntaxe :

  File "example.py", line 4
    message = "Hello, world
              ^
SyntaxError: unterminated string literal (detected at line 4)

Important : Remarquez que vous ne voyez pas « Program started! » dans la sortie. Python a détecté l’erreur de syntaxe avant d’exécuter le moindre code.

Comparez maintenant avec une exception à l’exécution :

python
# Exemple 2 : Exception à l'exécution
# Syntaxe valide, mais la variable n'existe pas
print("Program started!")
message = "Hello, world"
print(mesage)  # Faute de frappe : 'mesage' au lieu de 'message'

Output:

Program started!
Traceback (most recent call last):
  File "example.py", line 5, in <module>
    print(mesage)
          ^^^^^^
NameError: name 'mesage' is not defined

Important : Cette fois, vous voyez bien « Program started! » dans la sortie. Python a exécuté avec succès les deux premières instructions print et d’affectation (lignes 3-4), mais a rencontré un problème à la ligne 5 en essayant de trouver mesage.

La différence clé : Dans le premier exemple, Python n’a même pas essayé d’exécuter le code : il a trouvé l’erreur de syntaxe lors de l’analyse. Dans le second exemple, Python a démarré l’exécution du programme et a exécuté plusieurs lignes avant de rencontrer l’erreur à l’exécution.

Non

Oui

Non

Oui

Python lit votre code

Syntaxe valide ?

Erreur de syntaxe
Le programme ne s'exécute jamais

Le programme commence à s'exécuter

Problème pendant
l'exécution ?

Le programme se termine
avec succès

Exception à l'exécution
Le programme s'arrête

24.2) Types d’exceptions intégrées courantes

Python possède de nombreux types d’exceptions intégrées, chacune représentant un type de problème spécifique. Apprendre à reconnaître ces exceptions courantes vous aide à comprendre rapidement ce qui s’est mal passé et comment le corriger. Chaque type d’exception a un nom descriptif qui donne un indice sur le problème.

24.2.1) NameError : utiliser des noms non définis

Une NameError se produit lorsque vous essayez d’utiliser une variable, une fonction ou un autre nom que Python ne reconnaît pas. Cela signifie généralement que vous avez oublié de définir quelque chose, mal orthographié un nom, ou tenté d’utiliser quelque chose avant qu’il ne soit créé.

python
# Exemple 1 : oubli de définir une variable
print(greeting)  # NameError: name 'greeting' is not defined

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    print(greeting)
          ^^^^^^^^
NameError: name 'greeting' is not defined

Python vous dit qu’il ne sait pas ce qu’est greeting. Vous devez d’abord le créer :

python
# Version correcte
greeting = "Hello, Python!"
print(greeting)  # Output: Hello, Python!

Voici un exemple plus subtil avec une faute de frappe :

python
# Exemple 2 : faute de frappe dans le nom de variable
user_name = "Alice"
age = 30
 
print(f"{username} is {age} years old")  # NameError: name 'username' is not defined

Nous avons défini user_name (avec un underscore) mais tenté d’utiliser username (sans underscore). Python voit cela comme des noms complètement différents.

24.2.2) TypeError : mauvais type pour une opération

Une TypeError se produit lorsque vous essayez d’effectuer une opération sur une valeur du mauvais type. Par exemple, vous ne pouvez pas additionner une chaîne et un entier, ou appeler quelque chose qui n’est pas une fonction.

python
# Exemple 1 : mélanger des types incompatibles
age = 25
message = "You are " + age + " years old"  # TypeError

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    message = "You are " + age + " years old"
              ~~~~~~~~~~~~^~~~~
TypeError: can only concatenate str (not "int") to str

Python vous indique que l’opérateur + peut concaténer des chaînes avec des chaînes, mais pas des chaînes avec des entiers. Vous devez convertir l’entier en chaîne :

python
# Version correcte
age = 25
message = "You are " + str(age) + " years old"
print(message)  # Output: You are 25 years old

Les TypeError se produisent aussi lorsque vous passez le mauvais nombre d’arguments à une fonction :

python
# Exemple 3 : mauvais nombre d'arguments
def calculate_area(length, width):
    return length * width
 
area = calculate_area(5)  # TypeError: missing 1 required positional argument

Output:

Traceback (most recent call last):
  File "example.py", line 4, in <module>
    area = calculate_area(5)
TypeError: calculate_area() missing 1 required positional argument: 'width'

La fonction attend deux arguments, mais nous n’en avons fourni qu’un.

24.2.3) ValueError : bon type, mauvaise valeur

Une ValueError se produit lorsque vous passez une valeur du bon type, mais que la valeur elle-même est inappropriée pour l’opération. Le type est correct, mais la valeur spécifique n’a pas de sens dans ce contexte.

python
# Exemple 1 : convertir une chaîne invalide en entier
user_input = "twenty-five"
age = int(user_input)  # ValueError: invalid literal for int()

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    age = int(user_input)
ValueError: invalid literal for int() with base 10: 'twenty-five'

La fonction int() attend une chaîne, et nous lui avons donné une chaîne : le type est donc correct. Mais la chaîne "twenty-five" ne peut pas être convertie en entier car elle contient des lettres. La chaîne "25" fonctionnerait parfaitement :

python
# Version correcte
user_input = "25"
age = int(user_input)
print(age)  # Output: 25

Les ValueError se produisent aussi avec des méthodes de listes :

python
# Exemple 3 : supprimer un élément inexistant
fruits = ["apple", "banana", "orange"]
fruits.remove("grape")  # ValueError: 'grape' is not in list

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    fruits.remove("grape")
    ~~~~~~~~~~~~~^^^^^^^^^
ValueError: list.remove(x): x not in list

La méthode remove() attend une valeur qui existe dans la liste. Nous devrions d’abord vérifier :

python
# Version correcte
fruits = ["apple", "banana", "orange"]
if "grape" in fruits:
    fruits.remove("grape")
else:
    print("Grape not found in list")  # Output: Grape not found in list

24.2.4) IndexError : index de séquence invalide

Une IndexError se produit lorsque vous essayez d’accéder à une séquence (liste, tuple, chaîne) en utilisant un index qui n’existe pas. Rappelez-vous que Python utilise une indexation à partir de zéro, et que les index valides vont de 0 à len(sequence) - 1.

python
# Exemple 1 : index trop grand
colors = ["red", "green", "blue"]
print(colors[0])  # Output: red
print(colors[3])  # IndexError: list index out of range

Output:

red
Traceback (most recent call last):
  File "example.py", line 3, in <module>
    print(colors[3])
          ~~~~~~^^^
IndexError: list index out of range

La liste a trois éléments aux index 0, 1 et 2. L’index 3 n’existe pas. C’est une erreur très courante lorsqu’on oublie que l’indexation commence à 0 :

python
# Version correcte
colors = ["red", "green", "blue"]
print(colors[2])  # Output: blue (the third element)

24.2.5) KeyError : clé de dictionnaire manquante

Une KeyError se produit lorsque vous essayez d’accéder à une clé de dictionnaire qui n’existe pas. Contrairement aux listes où vous pouvez vérifier la longueur, les dictionnaires peuvent avoir n’importe quelles clés ; vous devez donc vérifier l’existence d’une clé avant d’y accéder.

python
# Exemple 1 : accéder à une clé inexistante
student = {
    "name": "Alice",
    "age": 20,
    "major": "Computer Science"
}
 
print(student["name"])   # Output: Alice
print(student["grade"])  # KeyError: 'grade'

Output:

Alice
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    print(student["grade"])
          ~~~~~~~^^^^^^^^^
KeyError: 'grade'

Le dictionnaire n’a pas de clé "grade". Vous pouvez vérifier si une clé existe d’abord :

python
# Version correcte avec 'in'
student = {
    "name": "Alice",
    "age": 20,
    "major": "Computer Science"
}
 
if "grade" in student:
    print(student["grade"])
else:
    print("Grade not available")  # Output: Grade not available

Ou utiliser la méthode get(), qui renvoie None (ou une valeur par défaut) au lieu de lever une erreur :

python
# Alternative avec get()
grade = student.get("grade")
if grade is not None:
    print(f"Grade: {grade}")
else:
    print("Grade not available")  # Output: Grade not available

Les KeyError se produisent souvent lors du traitement de données à la structure incohérente :

python
# Exemple 2 : traiter plusieurs enregistrements
students = [
    {"name": "Alice", "age": 20, "grade": "A"},
    {"name": "Bob", "age": 21},  # Clé 'grade' manquante
    {"name": "Carol", "age": 19, "grade": "B"}
]
 
for student in students:
    print(f"{student['name']}: {student['grade']}")  # KeyError sur Bob

Output:

Alice: A
Traceback (most recent call last):
  File "example.py", line 7, in <module>
    print(f"{student['name']}: {student['grade']}")
                                ~~~~~~~^^^^^^^^^
KeyError: 'grade'

Utilisez get() avec une valeur par défaut pour gérer proprement les clés manquantes :

python
# Version correcte
students = [
    {"name": "Alice", "age": 20, "grade": "A"},
    {"name": "Bob", "age": 21},
    {"name": "Carol", "age": 19, "grade": "B"}
]
 
for student in students:
    grade = student.get("grade", "Not assigned")
    print(f"{student['name']}: {grade}")

Output:

Alice: A
Bob: Not assigned
Carol: B

24.2.6) AttributeError : accès à un attribut invalide

Une AttributeError se produit lorsque vous essayez d’accéder à un attribut ou une méthode qui n’existe pas sur un objet. Cela arrive souvent quand vous confondez des méthodes entre différents types ou que vous faites une faute de frappe dans le nom d’un attribut.

python
# Exemple 1 : mauvaise méthode pour le type
numbers = [1, 2, 3, 4, 5]
numbers.append(6)  # Ceci fonctionne - les listes ont append()
print(numbers)     # Output: [1, 2, 3, 4, 5, 6]
 
text = "Hello"
text.append("!")   # AttributeError: 'str' object has no attribute 'append'

Output:

[1, 2, 3, 4, 5, 6]
Traceback (most recent call last):
  File "example.py", line 6, in <module>
    text.append("!")
    ^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'append'

Les chaînes n’ont pas de méthode append() parce qu’elles sont immuables. Vous devez utiliser la concaténation ou d’autres méthodes de chaîne :

python
# Version correcte
text = "Hello"
text = text + "!"  # Concaténation
print(text)        # Output: Hello!

Les AttributeError se produisent aussi avec des fautes de frappe :

python
# Exemple 2 : nom de méthode mal orthographié
message = "Python Programming"
result = message.uppper()  # AttributeError: 'str' object has no attribute 'uppper'

Output:

Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = message.uppper()
             ^^^^^^^^^^^^^^
AttributeError: 'str' object has no attribute 'uppper'. Did you mean: 'upper'?

Remarquez que Python 3.10+ suggère souvent l’orthographe correcte ! La bonne méthode est upper() :

python
# Version correcte
message = "Python Programming"
result = message.upper()
print(result)  # Output: PYTHON PROGRAMMING

24.2.7) ZeroDivisionError : division par zéro

Une ZeroDivisionError se produit lorsque vous essayez de diviser un nombre par zéro, ce qui est mathématiquement indéfini. Cela arrive souvent avec des entrées utilisateur ou des valeurs calculées que vous ne vous attendiez pas à voir à zéro.

python
# Exemple 1 : division directe par zéro
result = 10 / 0  # ZeroDivisionError: division by zero

Output:

Traceback (most recent call last):
  File "example.py", line 1, in <module>
    result = 10 / 0
             ~~~^~~
ZeroDivisionError: division by zero

Cela s’applique aussi à la division entière et au modulo :

python
# Exemple 2 : autres opérations de division
a = 10 // 0  # ZeroDivisionError
b = 10 % 0   # ZeroDivisionError

Un exemple plus réaliste implique des calculs :

python
# Exemple 3 : calculer une moyenne
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
scores = [85, 90, 78, 92]
print(calculate_average(scores))  # Output: 86.25
 
empty_scores = []
print(calculate_average(empty_scores))  # ZeroDivisionError

Output:

86.25
Traceback (most recent call last):
  File "example.py", line 9, in <module>
    print(calculate_average(empty_scores))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Une liste vide a une longueur de 0, ce qui provoque une division par zéro. Vérifiez toujours cette condition :

python
# Version correcte
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Or return None, or raise a more descriptive error
    
    total = sum(numbers)
    count = len(numbers)
    return total / count
 
scores = [85, 90, 78, 92]
print(calculate_average(scores))  # Output: 86.25
 
empty_scores = []
print(calculate_average(empty_scores))  # Output: 0

Types d'exceptions courants

NameError
Variable/fonction non définie

TypeError
Mauvais type pour l'opération

ValueError
Bon type, mauvaise valeur

IndexError
Index de séquence invalide

KeyError
Clé de dictionnaire manquante

AttributeError
Attribut/méthode invalide

ZeroDivisionError
Division par zéro

Comprendre ces types d’exceptions courants vous aide à diagnostiquer rapidement les problèmes. Quand vous voyez une exception, le nom du type vous indique immédiatement quelle catégorie de problème s’est produite, et le message d’erreur fournit des détails précis sur ce qui s’est mal passé.

24.3) Lire et interpréter les tracebacks en détail

Lorsqu’une exception à l’exécution se produit, Python ne se contente pas de vous dire ce qui s’est mal passé : il fournit un traceback (traceback) détaillé qui montre exactement comment votre programme en est arrivé là. Apprendre à lire les tracebacks est essentiel pour un débogage efficace. Un traceback ressemble à une piste de miettes de pain montrant le chemin emprunté par votre programme avant de rencontrer l’erreur.

24.3.1) Anatomie d’un traceback

Commençons par un exemple simple et examinons chaque partie du traceback :

python
# Programme simple avec une erreur
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
def process_order(item_price, discount):
    discounted_price = calculate_discount(item_price, discount)
    tax = discounted_price * 0.08
    total = discounted_price + tax
    return total
 
# Programme principal
original_price = "50"  # Oups ! Cela devrait être un nombre
discount_rate = 10
final_cost = process_order(original_price, discount_rate)
print(f"Final cost: ${final_cost:.2f}")

Output:

Traceback (most recent call last):
  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~
TypeError: can't multiply sequence by non-int of type 'float'

Décomposons chaque composant de ce traceback :

1. L’en-tête : "Traceback (most recent call last):"

Cette ligne vous indique que ce qui suit est un traceback : un enregistrement des appels de fonctions. L’expression "most recent call last" signifie que le traceback est affiché dans l’ordre chronologique : la première fonction appelée apparaît en premier, et l’endroit où l’erreur s’est réellement produite apparaît en dernier.

2. La pile d’appels (lecture de haut en bas) :

  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)
                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

C’est le premier appel de fonction dans la chaîne. Il montre :

  • Nom de fichier : "example.py" - où se trouve le code
  • Numéro de ligne : line 16 - la ligne exacte qui a effectué cet appel
  • Contexte : in <module> - ce code est au niveau supérieur (pas à l’intérieur d’une fonction)
  • Code : la ligne réellement exécutée
  • Surlignage : les caractères ^ pointent la partie précise de la ligne concernée

Le contexte <module> signifie que ce code s’exécute au niveau du module (la partie principale de votre script), pas dans une fonction.

  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

C’est le deuxième appel de fonction. La fonction process_order a été appelée depuis la ligne 16, et maintenant nous sommes à l’intérieur de cette fonction à la ligne 8, où elle appelle calculate_discount.

  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

C’est ici que l’erreur s’est réellement produite. Nous sommes maintenant dans la fonction calculate_discount à la ligne 2, et c’est cette ligne qui a causé le problème.

3. Le message d’erreur :

TypeError: can't multiply sequence by non-int of type 'float'

C’est l’erreur qui s’est effectivement produite :

  • Type d’exception : TypeError - vous indique la catégorie d’erreur
  • Description : le reste explique précisément ce qui s’est mal passé

Dans ce cas, Python nous dit que nous avons tenté de multiplier une séquence (une chaîne, ici) par un float, ce qui n’est pas autorisé.

24.3.2) Lire le traceback de bas en haut

Même si le traceback est imprimé dans l’ordre chronologique (de haut en bas), les programmeurs expérimentés le lisent souvent de bas en haut car l’erreur réelle est en bas, et les lignes au-dessus montrent comment on en est arrivé là.

Relisons notre traceback précédent de bas en haut :

Étape 1 : Commencez par le message d’erreur

TypeError: can't multiply sequence by non-int of type 'float'

« D’accord, nous avons essayé de multiplier une séquence par un float. Ce n’est pas autorisé. »

Étape 2 : Regardez où l’erreur s’est produite

  File "example.py", line 2, in calculate_discount
    discount_amount = price * (discount_percent / 100)
                      ~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~

« L’erreur s’est produite dans la fonction calculate_discount à la ligne 2. Nous essayons de multiplier price par quelque chose. »

Étape 3 : Remontez pour voir comment on en est arrivé là

  File "example.py", line 8, in process_order
    discounted_price = calculate_discount(item_price, discount)

« La fonction calculate_discount a été appelée depuis process_order à la ligne 8, en passant item_price comme paramètre price. »

Étape 4 : Continuez à remonter

  File "example.py", line 16, in <module>
    final_cost = process_order(original_price, discount_rate)

« Et process_order a été appelée depuis le programme principal à la ligne 16, en passant original_price comme item_price. »

Étape 5 : Trouvez la cause racine

Nous pouvons maintenant remonter le problème : original_price est "50" (une chaîne), qui est passée comme item_price à process_order, qui la passe comme price à calculate_discount, où nous essayons de la multiplier par un float. La solution est de faire de original_price un nombre :

python
# Version corrigée
def calculate_discount(price, discount_percent):
    discount_amount = price * (discount_percent / 100)
    final_price = price - discount_amount
    return final_price
 
def process_order(item_price, discount):
    discounted_price = calculate_discount(item_price, discount)
    tax = discounted_price * 0.08
    total = discounted_price + tax
    return total
 
# Programme principal - correction du type
original_price = 50  # Maintenant c'est un nombre, pas une chaîne
discount_rate = 10
final_cost = process_order(original_price, discount_rate)
print(f"Final cost: ${final_cost:.2f}")  # Output: Final cost: $48.60

Lire le traceback

1. Lire le message d'erreur
Dernière ligne
2. Trouver l'emplacement de l'erreur
Dernier bloc de code
3. Suivre la pile d'appels
Remonter
4. Formuler une hypothèse
Qu'est-ce qui s'est passé ?
5. Vérifier et corriger
Tester votre solution

Comprendre comment lire les tracebacks les transforme de murs de texte intimidants en outils de débogage utiles. Chaque ligne fournit des informations précieuses sur le chemin d’exécution de votre programme, et avec de la pratique, vous pourrez rapidement identifier et corriger les problèmes en suivant les indications du traceback.

24.4) Comment les exceptions modifient le flux normal d’un programme

Lorsqu’une exception se produit, elle ne se contente pas d’arrêter votre programme : elle modifie fondamentalement la manière dont le programme s’exécute. Comprendre ce comportement est crucial pour écrire du code robuste et pour comprendre ce qui se passe quand des erreurs surviennent.

24.4.1) Flux normal d’un programme vs flux avec exception

En exécution normale, Python exécute votre code ligne par ligne, de haut en bas :

python
# Flux normal du programme
print("Step 1: Starting calculation")
result = 10 + 5
print(f"Step 2: Result is {result}")
final = result * 2
print(f"Step 3: Final value is {final}")
print("Step 4: Program complete")

Output:

Step 1: Starting calculation
Step 2: Result is 15
Step 3: Final value is 30
Step 4: Program complete

Chaque ligne s’exécute dans l’ordre. Voyons maintenant ce qui se passe lorsqu’une exception se produit :

python
# Flux du programme avec une exception
print("Step 1: Starting calculation")
result = 10 / 0  # Ceci lève une ZeroDivisionError
print(f"Step 2: Result is {result}")  # Ceci ne s'exécute jamais
final = result * 2  # Ceci ne s'exécute jamais
print(f"Step 3: Final value is {final}")  # Ceci ne s'exécute jamais
print("Step 4: Program complete")  # Ceci ne s'exécute jamais

Output:

Step 1: Starting calculation
Traceback (most recent call last):
  File "example.py", line 2, in <module>
    result = 10 / 0
             ~~~^~~
ZeroDivisionError: division by zero

Remarquez que seule la première instruction print s’est exécutée. Dès que l’exception s’est produite à la ligne 2, Python a cessé d’exécuter le reste du code. L’exception a interrompu le flux normal.

24.4.2) Les exceptions se propagent vers le haut dans la pile d’appels

Lorsqu’une exception se produit dans une fonction, Python ne s’arrête pas uniquement à cette fonction : elle se propage (remonte) dans la pile d’appels jusqu’à ce que quelque chose la gère ou que le programme se termine.

python
# Exemple 1 : exception se propageant à travers des fonctions
def calculate_average(numbers):
    total = sum(numbers)
    count = len(numbers)
    return total / count  # Peut lever ZeroDivisionError
 
def process_scores(score_list):
    print("Processing scores...")
    avg = calculate_average(score_list)
    print(f"Average calculated: {avg}")
    return avg
 
def main():
    print("Program starting")
    scores = []  # Liste vide
    result = process_scores(scores)
    print(f"Final result: {result}")
    print("Program ending")
 
main()

Output:

Program starting
Processing scores...
Traceback (most recent call last):
  File "example.py", line 18, in <module>
    main()
  File "example.py", line 14, in main
    result = process_scores(scores)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 9, in process_scores
    avg = calculate_average(score_list)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    return total / count
           ~~~~~~^~~~~~~
ZeroDivisionError: division by zero

Suivons ce qui s’est passé :

  1. main() a commencé à s’exécuter et a affiché "Program starting"
  2. main() a appelé process_scores()
  3. process_scores() a affiché "Processing scores..."
  4. process_scores() a appelé calculate_average()
  5. calculate_average() a tenté de diviser par zéro
  6. L’exception s’est produite et s’est propagée vers le haut :
    • calculate_average() s’est arrêtée immédiatement (n’a pas renvoyé de valeur)
    • Le contrôle est revenu à process_scores(), mais pas normalement : l’exception a continué à se propager
    • process_scores() s’est arrêtée immédiatement (le print après calculate_average() ne s’est jamais exécuté)
    • Le contrôle est revenu à main(), mais à nouveau, l’exception a continué à se propager
    • main() s’est arrêtée immédiatement (les prints après process_scores() ne se sont jamais exécutés)
  7. Le programme s’est terminé avec le traceback

Aucun code après l’exception ne s’est exécuté dans aucune des fonctions. L’exception a « remonté » à travers tous les appels de fonction jusqu’à atteindre le niveau supérieur et terminer le programme.

24.5) État d’esprit de débogage : traiter les erreurs comme des informations, pas comme des échecs

L’une des compétences les plus importantes en programmation n’est pas d’écrire du code parfait : c’est d’apprendre à travailler efficacement avec du code imparfait. Chaque programmeur, quel que soit son niveau d’expérience, écrit du code qui produit des erreurs. La différence entre les programmeurs qui peinent et ceux qui sont efficaces ne réside pas dans l’évitement des erreurs, mais dans la façon dont ils y réagissent.

24.5.1) Les erreurs ne sont pas des échecs

Quand vous apprenez à programmer, il est naturel de vous sentir frustré lorsque vous rencontrez des erreurs. Vous pouvez avoir l’impression d’avoir fait quelque chose de mal ou de ne pas « y arriver ». Cet état d’esprit est contre-productif et, plus important encore, inexact.

Les erreurs ne sont pas des échecs : ce sont des retours d’information.

Considérez les erreurs comme un GPS qui recalcule votre itinéraire. Quand vous ratez un virage, le GPS ne dit pas « Vous avez échoué ! ». Il dit « Recalcul de l’itinéraire » et vous donne de nouvelles directions. Les messages d’erreur de Python fonctionnent de la même manière : ils vous indiquent que le chemin que vous avez emprunté n’a pas fonctionné, et ils vous fournissent des informations pour vous aider à trouver un chemin qui fonctionne.

Considérez cet exemple simple :

python
# Première tentative pour calculer une moyenne
def calculate_average(numbers):
    total = sum(numbers)
    average = total / len(numbers)
    return average
 
scores = []
result = calculate_average(scores)
print(f"Average: {result}")

Output:

Traceback (most recent call last):
  File "example.py", line 8, in <module>
    result = calculate_average(scores)
             ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "example.py", line 4, in calculate_average
    average = total / len(numbers)
              ~~~~~~^~~~~~~~~~~~~~
ZeroDivisionError: division by zero

Cette erreur ne vous dit pas que vous êtes un mauvais programmeur. Elle vous dit quelque chose de spécifique et d’utile : « Vous avez tenté de diviser par zéro, ce qui arrive quand la liste est vide. Vous devez gérer ce cas. »

Fort de cette information, vous pouvez améliorer votre code :

python
# Version améliorée basée sur le retour d'information de l'erreur
def calculate_average(numbers):
    if len(numbers) == 0:
        return 0  # Or return None, or raise a more descriptive error
    
    total = sum(numbers)
    average = total / len(numbers)
    return average
 
scores = []
result = calculate_average(scores)
print(f"Average: {result}")  # Output: Average: 0

L’erreur vous a aidé à écrire un meilleur code. Sans cette erreur, vous n’auriez peut-être pas réalisé que votre fonction ne pouvait pas gérer des listes vides.

24.5.2) Chaque erreur vous apprend quelque chose

Chaque erreur que vous rencontrez vous apprend quelque chose sur Python, sur votre code, ou sur la programmation en général. Regardons plusieurs exemples de ce que différentes erreurs nous apprennent :

Exemple 1 : Apprendre à propos des types

python
# Tenter d'additionner des types incompatibles
age = 25
message = "You are " + age + " years old"

Output:

TypeError: can only concatenate str (not "int") to str

Ce que cela enseigne : Python a des règles de type strictes. Vous ne pouvez pas mélanger chaînes et nombres dans une concaténation. Cette erreur vous apprend la compatibilité des types et vous introduit au concept de conversion de type.

Exemple 2 : Apprendre à propos des structures de données

python
# Tenter d'accéder à un dictionnaire comme à une liste
student = {"name": "Alice", "age": 20}
first_value = student[0]

Output:

KeyError: 0

Ce que cela enseigne : Les dictionnaires utilisent des clés, pas des index numériques. Cette erreur vous apprend la différence entre dictionnaires et listes, et comment accéder correctement aux valeurs d’un dictionnaire.

Exemple 3 : Apprendre à propos de la portée

python
# Tenter d'utiliser une variable avant de la définir
def greet():
    print(f"Hello, {name}!")
 
greet()
name = "Alice"

Output:

NameError: name 'name' is not defined

Ce que cela enseigne : Les variables doivent être définies avant d’être utilisées, et l’ordre d’exécution compte. Cette erreur vous apprend la portée des variables et l’importance de l’initialisation.

Chacune de ces erreurs fournit des informations spécifiques et actionnables qui vous aident à mieux comprendre Python. Plutôt que de les voir comme des obstacles, considérez-les comme des opportunités d’apprentissage.

24.5.3) Adopter l’état d’esprit de débogage

Les programmeurs professionnels passent une part importante de leur temps à déboguer. Ce n’est pas un signe de faiblesse : c’est un élément central du métier. Les meilleurs programmeurs ne sont pas ceux qui ne font jamais d’erreurs ; ce sont ceux qui :

  1. S’attendent aux erreurs : ils savent qu’elles arriveront et ne sont ni surpris ni découragés
  2. Lisents les erreurs attentivement : ils extraient un maximum d’informations des messages d’erreur
  3. Déboguent systématiquement : ils suivent un processus logique plutôt que de faire des changements au hasard
  4. Apprennent des erreurs : ils utilisent chaque erreur comme une occasion de mieux comprendre Python
  5. Restent curieux : ils demandent « Pourquoi cela s’est-il produit ? » plutôt que seulement « Comment le corriger ? »

Non

Oui

Non

Oui

Rencontrer une erreur

Lire le message d'erreur
attentivement

Comprendre ce qui
s'est mal passé

Formuler une hypothèse
sur la cause

Tester l'hypothèse
avec une sortie de debug

Hypothèse
correcte ?

Implémenter le correctif

Tester la solution

Fonctionne
correctement ?

Apprendre de
l'expérience

Avancer
avec confiance

Rappelez-vous : chaque erreur est une opportunité d’apprendre quelque chose de nouveau sur Python, sur la programmation, ou sur la résolution de problèmes. Accueillez les erreurs comme un retour d’information précieux, abordez-les de manière systématique, et célébrez vos succès de débogage. Cet état d’esprit vous accompagnera tout au long de votre parcours en programmation.


Comprendre les erreurs et les tracebacks est fondamental pour devenir un programmeur Python efficace. Dans ce chapitre, nous avons appris à distinguer les erreurs de syntaxe (problèmes de structure du code) et les exceptions à l’exécution (problèmes pendant l’exécution), à reconnaître les types d’exceptions courants et ce qu’ils indiquent, à lire et interpréter des tracebacks détaillés pour trouver la cause racine des problèmes, à comprendre comment les exceptions modifient le flux du programme en se propageant vers le haut dans la pile d’appels, et à développer un état d’esprit de débogage qui traite les erreurs comme des informations précieuses plutôt que comme des échecs.

Ces compétences constituent la base du chapitre suivant, où nous apprendrons à gérer les exceptions de manière élégante en utilisant des blocs try et except, permettant à nos programmes de se remettre d’erreurs et de continuer à s’exécuter. Mais avant de pouvoir gérer les exceptions efficacement, nous devons les comprendre en profondeur — et c’est exactement ce que nous avons accompli ici.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai