Chapitre 820 minDébutant
Erreurs et exceptions : lire un traceback, try et except
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- lire un traceback de bas en haut et trouver la cause d'une erreur ;
- intercepter une exception précise avec try et except ;
- lever une exception avec un message utile ;
- terminer un script avec un code de retour testable.
3 exercices corrigés2 schémas
Une exception est l'objet que Python crée quand une instruction ne peut pas s'exécuter (fichier absent, conversion impossible, clé inconnue) ; si personne ne l'intercepte, le programme s'arrête et affiche un traceback, le rapport d'erreur. Ce chapitre apprend à lire un traceback, à reconnaître les exceptions les plus fréquentes, à les intercepter avec try et except, à en lever avec raise, et à terminer un script avec un code de retour qu'un autre programme peut tester.
Lire un traceback
Le script suivant calcule la moyenne des taux d'occupation lus dans un fichier, dont une ligne contient N/A :
# moyenne.py : moyenne des taux d'occupation lus dans un fichier
def lire_taux(ligne):
nom, taux = ligne.strip().split(";")
return float(taux)
def moyenne_occupation(chemin):
with open(chemin, encoding="utf-8") as f:
taux = [lire_taux(ligne) for ligne in f]
return sum(taux) / len(taux)
print(moyenne_occupation("occupation.txt"))
Traceback (most recent call last):
File "/home/etudiant/cours-python/moyenne.py", line 13, in <module>
print(moyenne_occupation("occupation.txt"))
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
File "/home/etudiant/cours-python/moyenne.py", line 9, in moyenne_occupation
taux = [lire_taux(ligne) for ligne in f]
~~~~~~~~~^^^^^^^
File "/home/etudiant/cours-python/moyenne.py", line 4, in lire_taux
return float(taux)
ValueError: could not convert string to float: 'N/A'
Un traceback se lit de bas en haut :
- La dernière ligne donne le type de l'exception (
ValueError) et son message : la conversion en flottant de la chaîne'N/A'est impossible. - Le bloc juste au-dessus montre où l'erreur s'est produite : fichier, numéro de ligne, fonction (
lire_taux), et l'instruction elle-même. - Les blocs supérieurs retracent le chemin des appels : le module a appelé
moyenne_occupation(ligne 13), qui a appelélire_taux(ligne 9). Les^^^et~~~soulignent la partie précise de l'instruction en cause.
Ici, le code est juste et c'est la donnée qui est invalide. Avant de corriger, demande-toi toujours lequel des deux est fautif : un traceback dans ton code ne veut pas dire que ton code est faux.
Les exceptions les plus fréquentes
>>> serveurs = ["srv-ad", "srv-ad2"]
>>> print(serveur)
Traceback (most recent call last):
File "<python-input-1>", line 1, in <module>
print(serveur)
^^^^^^^
NameError: name 'serveur' is not defined. Did you mean: 'serveurs'?
>>> serveurs[2]
Traceback (most recent call last):
File "<python-input-2>", line 1, in <module>
serveurs[2]
~~~~~~~~^^^
IndexError: list index out of range
>>> {"srv-ad": "192.168.10.177"}["srv-nas"]
Traceback (most recent call last):
File "<python-input-3>", line 1, in <module>
{"srv-ad": "192.168.10.177"}["srv-nas"]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
KeyError: 'srv-nas'
>>> int("12 Go")
Traceback (most recent call last):
File "<python-input-4>", line 1, in <module>
int("12 Go")
~~~^^^^^^^^^
ValueError: invalid literal for int() with base 10: '12 Go'
>>> "Taux : " + 87.5
Traceback (most recent call last):
File "<python-input-5>", line 1, in <module>
"Taux : " + 87.5
~~~~~~~~~~^~~~~~
TypeError: can only concatenate str (not "float") to str
>>> serveurs.push("srv-nas")
Traceback (most recent call last):
File "<python-input-6>", line 1, in <module>
serveurs.push("srv-nas")
^^^^^^^^^^^^^
AttributeError: 'list' object has no attribute 'push'. Did you mean '.append'?
>>> 10 / 0
Traceback (most recent call last):
File "<python-input-7>", line 1, in <module>
10 / 0
~~~^~~
ZeroDivisionError: division by zero
>>> open("inventaire-2025.csv")
Traceback (most recent call last):
File "<python-input-8>", line 1, in <module>
open("inventaire-2025.csv")
~~~~^^^^^^^^^^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'inventaire-2025.csv'
>>> import pathlb
Traceback (most recent call last):
File "<python-input-9>", line 1, in <module>
import pathlb
ModuleNotFoundError: No module named 'pathlb'. Did you mean: 'pathlib'?
| Exception | Cause habituelle | Piste |
|---|---|---|
NameError | Variable ou fonction inconnue : faute de frappe, variable pas encore créée | Lis la suggestion « Did you mean » |
IndexError | Indice hors de la liste | La liste a len(liste) éléments, d'indices 0 à len(liste) - 1 |
KeyError | Clé absente du dictionnaire | get(), ou in avant d'accéder |
ValueError | Bon type, mauvaise valeur : int("12 Go") | Nettoie ou valide la donnée |
TypeError | Opération entre types incompatibles, mauvais nombre d'arguments | Convertis : str(87.5), ou une f-string |
AttributeError | Méthode qui n'existe pas pour ce type, souvent None.quelque_chose | print(type(x)) |
ZeroDivisionError | Division par zéro, souvent une moyenne de liste vide | Teste la longueur avant |
FileNotFoundError | Fichier absent ou chemin relatif mal compris | Vérifie le dossier courant, utilise Path(__file__).parent |
PermissionError | Droits insuffisants sur le fichier | Droits du compte qui exécute le script |
ModuleNotFoundError | Module non installé, ou installé dans un autre environnement | Chapitre 2 |
Depuis Python 3.10, les messages suggèrent la correction (« Did you mean: 'serveurs'? ») ; Python 3.15 va plus loin et reconnaît les méthodes d'autres langages, comme le push du JavaScript.
try, except, else, finally
Pour traiter une erreur prévisible au lieu de laisser le script s'arrêter, place l'instruction risquée dans un bloc try et dis dans except quoi faire si une exception précise se produit :
# moyenne_robuste.py : ignorer les lignes invalides au lieu de s'arrêter
def moyenne_occupation(chemin):
taux = []
with open(chemin, encoding="utf-8") as f:
for numero, ligne in enumerate(f, start=1):
try:
nom, valeur = ligne.strip().split(";")
taux.append(float(valeur))
except ValueError as erreur:
print(f"Ligne {numero} ignorée ({ligne.strip()}) : {erreur}")
return sum(taux) / len(taux)
for fichier in ["occupation.txt", "occupation-2025.txt"]:
try:
moyenne = moyenne_occupation(fichier)
except FileNotFoundError:
print(f"{fichier} : fichier introuvable")
else:
print(f"{fichier} : moyenne {moyenne:.1f} %")
finally:
print("--")
Ligne 3 ignorée (srv-web;N/A) : could not convert string to float: 'N/A'
occupation.txt : moyenne 70.7 %
--
occupation-2025.txt : fichier introuvable
--
| Bloc | Quand il s'exécute |
|---|---|
try | Toujours : c'est le code surveillé. Garde-le court : seulement l'instruction qui peut échouer |
except TypeErreur as e | Si une exception de ce type (ou d'un sous-type) se produit dans le try ; e contient l'exception, str(e) son message |
else | Si le try s'est terminé sans exception |
finally | Dans tous les cas, même après un return ou une exception non interceptée : libérer une ressource, écrire un journal |
Plusieurs except peuvent se suivre, du plus précis au plus général ; except (ValueError, KeyError): en traite deux de la même façon. Le premier except qui correspond gagne.
Lever une exception avec raise
Une fonction qui reçoit des valeurs absurdes doit le dire, plutôt que de renvoyer un résultat faux. raise lève une exception ; tu peux utiliser les exceptions existantes (ValueError pour une valeur invalide) ou créer la tienne en héritant d'Exception :
# raise_demo.py : refuser des paramètres incohérents, avec une exception à soi
class ConfigurationInvalide(Exception):
"""Erreur dans le fichier de configuration du contrôle des disques."""
def verifier_seuils(alerte, critique):
if not 0 < alerte < critique <= 100:
raise ConfigurationInvalide(
f"seuils incohérents : alerte={alerte}, critique={critique}"
)
for alerte, critique in [(80, 90), (99, 90)]:
try:
verifier_seuils(alerte, critique)
print(f"Seuils {alerte}/{critique} acceptés")
except ConfigurationInvalide as erreur:
print("Erreur :", erreur)
verifier_seuils(95, 90)
Seuils 80/90 acceptés
Erreur : seuils incohérents : alerte=99, critique=90
Traceback (most recent call last):
File "/home/etudiant/cours-python/raise_demo.py", line 20, in <module>
verifier_seuils(95, 90)
~~~~~~~~~~~~~~~^^^^^^^^
File "/home/etudiant/cours-python/raise_demo.py", line 8, in verifier_seuils
raise ConfigurationInvalide(
f"seuils incohérents : alerte={alerte}, critique={critique}"
)
ConfigurationInvalide: seuils incohérents : alerte=95, critique=90
Une exception à toi, comme ConfigurationInvalide, permet à l'appelant de la distinguer des autres. Le message doit dire quoi est faux et avec quelles valeurs : « seuils incohérents : alerte=95, critique=90 » se corrige en dix secondes, « erreur de configuration » en dix minutes. La classe ne contient qu'une docstring : l'héritage est expliqué au chapitre 14.
Code de retour d'un script
Un programme qui se termine renvoie au système un code de retour : 0 si tout s'est bien passé, un autre nombre sinon. Le Planificateur de tâches, cron, un pipeline d'intégration continue ou un autre script lisent ce code pour savoir si la tâche a réussi. sys.exit(code) termine le script avec le code choisi ; un script qui s'arrête sur une exception non interceptée renvoie 1.
# verif.py : un script qui renvoie un code de retour
import sys
from pathlib import Path
fichier = Path("inventaire.csv")
if not fichier.exists():
print(f"ERREUR : {fichier} introuvable", file=sys.stderr)
sys.exit(2)
print("Inventaire présent")
sys.exit(0)
python verif.py
echo "Code de retour : $?" # sous Linux ; sous PowerShell : $LASTEXITCODE
ERREUR : inventaire.csv introuvable
Code de retour : 2
Les messages d'erreur vont sur la sortie d'erreur (file=sys.stderr), séparée de la sortie normale : quand la sortie du script est redirigée dans un fichier, les erreurs restent visibles. Le même principe existe en PowerShell et en Bash (gestion des erreurs dans un script).
Exercices
Exercice 1 sur 3 Quelle exception ? Réussi À revoir
Pour chaque ligne, donne le type d'exception levée, sans exécuter. serveurs vaut ["srv-ad", "srv-ad2"] et ips vaut {"srv-ad": "192.168.10.177"}.
serveurs[len(serveurs)]ips["SRV-AD"]float("87,5")len(42)sum([]) / len([])ips.append("srv-nas")
Voir la réponseMasquer la réponse de l'exercice 1
IndexError: la liste a 2 éléments, d'indices 0 et 1 ;len(serveurs)vaut 2.KeyError: 'SRV-AD': les clés sont sensibles à la casse.ValueError: Python attend un point décimal. Une valeur venue d'un tableur français se convertit avecfloat(valeur.replace(",", ".")).TypeError: object of type 'int' has no len().ZeroDivisionError: la moyenne d'une liste vide. Testeif liste:avant de diviser.AttributeError: un dictionnaire n'a pas de méthodeappend; on ajoute avecips["srv-nas"] = "192.168.10.183".
Tu avais trouvé ?
Exercice 2 sur 3 Une saisie robuste Réussi À revoir
Écris un script qui demande un numéro de VLAN et redemande tant que la saisie n'est pas un entier de 1 à 4094, avec un message différent selon que la saisie n'est pas un nombre ou est hors plage. Teste avec abc, 5000, puis 21.
Voir la réponseMasquer la réponse de l'exercice 2
# ex_vlan.py : redemander tant que la saisie n'est pas un numéro de VLAN valide
while True:
saisie = input("Numéro de VLAN (1 à 4094) ? ")
try:
vlan = int(saisie)
except ValueError:
print(f"« {saisie} » n'est pas un nombre entier.")
continue
if 1 <= vlan <= 4094:
break
print(f"{vlan} est hors de la plage 1 à 4094.")
print(f"VLAN {vlan} retenu.")
Numéro de VLAN (1 à 4094) ? abc
« abc » n'est pas un nombre entier.
Numéro de VLAN (1 à 4094) ? 5000
5000 est hors de la plage 1 à 4094.
Numéro de VLAN (1 à 4094) ? 21
VLAN 21 retenu.
while True boucle jusqu'au break. Le try ne contient que la conversion, la seule instruction qui peut lever ValueError ; la vérification de la plage est un simple if. Les identifiants 0 et 4095 sont réservés par la norme 802.1Q, d'où la plage 1 à 4094.
Tu avais trouvé ?
Exercice 3 sur 3 L'erreur avalée Réussi À revoir
Ce script de contrôle affiche 0 machines à contrôler, alors que l'inventaire en compte 8. Il tourne pourtant chaque nuit « sans erreur ». Trouve le problème et corrige-le.
import csv
def charger_inventaire(chemin):
try:
with open(chemin, encoding="utf-8-sig", newline="") as f:
return list(csv.DictReader(f, delimiter=";"))
except:
return []
machines = charger_inventaire("inventaire.csv")
print(len(machines), "machines à contrôler")
Voir la réponseMasquer la réponse de l'exercice 3
Le except: nu avale toutes les erreurs : ici, le fichier n'est pas dans le dossier courant de la tâche planifiée, open() lève FileNotFoundError, et la fonction renvoie une liste vide comme si l'inventaire était vide. Le script se termine avec le code 0, la tâche est « réussie », et plus rien n'est contrôlé.
0 machines à contrôler
Code de retour : 0
La correction retire le try de la fonction, qui laisse remonter l'erreur, et la traite là où on sait quoi faire : arrêter le script avec un message clair et un code de retour non nul, que la supervision verra.
import csv
import sys
def charger_inventaire(chemin):
with open(chemin, encoding="utf-8-sig", newline="") as f:
return list(csv.DictReader(f, delimiter=";"))
try:
machines = charger_inventaire("inventaire.csv")
except FileNotFoundError as erreur:
print(f"ERREUR : {erreur}", file=sys.stderr)
sys.exit(1)
print(len(machines), "machines à contrôler")
ERREUR : [Errno 2] No such file or directory: 'inventaire.csv'
Code de retour : 1
Reste à rendre le chemin indépendant du dossier courant : Path(__file__).parent / "inventaire.csv".
Tu avais trouvé ?