Exceptions : lire un traceback, try / except / else / finally
Lire un traceback dans le bon sens, attraper une exception avec try / except, savoir quand passent else et finally, et ranger les except du plus précis au plus général.
Mis à jour le
Pourquoi on voit ça
Un script DevOps travaille avec des choses qui échouent : un fichier absent, un port écrit "abc" dans un inventaire, un champ manquant dans un JSON. Quand ça arrive, Python lève une exception : il arrête ce qu’il faisait et affiche un traceback.
Deux compétences à avoir :
- lire un traceback, pour trouver la cause en dix secondes ;
- attraper une exception avec
try / except, pour décider quoi faire au lieu de laisser le script s’arrêter.
Au contrôle, la réponse attendue est parfois une exception. Tu dois donc savoir quelle ligne plante, avec quel type, et quels blocs s’exécutent ensuite.
Lire un traceback : de bas en haut
Voici un script fleet.py :
def owner_of(server):
return server["owner"]
servers = [{"name": "web-1", "owner": "alice"}, {"name": "db-1"}]
for s in servers:
print(owner_of(s))
alice
Traceback (most recent call last):
File "fleet.py", line 6, in <module>
print(owner_of(s))
~~~~~~~~^^^
File "fleet.py", line 2, in owner_of
return server["owner"]
~~~~~~^^^^^^^^^
KeyError: 'owner'
Le réflexe : commence par la dernière ligne.
| Où | Ce que tu y lis |
|---|---|
| dernière ligne | le type de l’exception et le message : KeyError: 'owner' |
| juste au-dessus | la ligne qui a planté : server["owner"], ligne 2, dans owner_of |
| plus haut | le chemin : qui a appelé qui (la ligne 6 a appelé owner_of) |
« most recent call last » veut dire : l’appel le plus récent est en bas. Le haut du traceback est le point de départ, le bas est l’endroit de l’accident.
Remarque aussi que alice s’est affiché avant le traceback : le premier tour de boucle a réussi. C’est le deuxième serveur, db-1, qui n’a pas de clé owner.
Les exceptions courantes
| Exception | Quand | Exemple |
|---|---|---|
ValueError |
bon type, mais valeur inutilisable | int("abc") |
TypeError |
mauvais type | "1" + 1 |
KeyError |
clé absente d’un dictionnaire (avec les crochets) | s["owner"] |
FileNotFoundError |
fichier introuvable | open("absent.txt") |
La différence entre ValueError et TypeError : int() accepte un texte, donc int("abc") a le bon type mais un mauvais contenu. À l’inverse, "1" + 1 mélange deux types que + ne sait pas combiner.
Attraper : try / except
try:
port = int("abc")
print("ok")
except ValueError:
print("invalide")
print("suite")
invalide
suite
Déroule :
- Python essaie le bloc
try. int("abc")lèveValueError. Le reste dutryest sauté :print("ok")ne s’exécute pas.- Python cherche un
exceptqui correspond.except ValueErrorconvient : son bloc s’exécute. - Le programme continue après le bloc :
suites’affiche.
S’il n’y a pas d’exception, le try s’exécute en entier et le except est ignoré.
Un except n’attrape que son type
s = {"name": "web-1"}
try:
print(s["owner"])
except ValueError:
print("valeur")
print("suite")
Traceback (most recent call last):
File "fleet.py", line 3, in <module>
print(s["owner"])
~^^^^^^^^^
KeyError: 'owner'
L’exception levée est une KeyError. Le except ValueError ne la concerne pas. Elle n’est donc pas attrapée : le programme s’arrête, et suite ne s’affiche jamais.
else et finally
Le bloc complet a quatre parties.
| Bloc | S’exécute… |
|---|---|
try |
toujours (jusqu’à la première exception) |
except |
seulement si une exception de ce type est levée |
else |
seulement s’il n’y a eu aucune exception |
finally |
toujours, erreur ou pas |
for texte in ["80", "abc"]:
try:
port = int(texte)
except ValueError:
print("invalide")
else:
print("ok", port)
finally:
print("fin")
ok 80
fin
invalide
fin
- Avec
"80": pas d’exception →else, puisfinally. - Avec
"abc":ValueError→except, puisfinally. Leelseest sauté.
À quoi ça sert : else contient la suite « quand tout va bien », et finally le ménage à faire dans tous les cas. finally passe même si l’exception n’est pas attrapée : il s’exécute, puis le traceback s’affiche.
Hiérarchie et ordre des except
Les exceptions sont rangées en famille. Exception est le parent de presque toutes les autres : ValueError, TypeError et KeyError sont ses enfants. FileNotFoundError est un enfant de OSError, lui-même enfant de Exception.
Deux règles en découlent :
- Un
exceptattrape son type et tous ses enfants.except Exceptionattrape donc presque tout. - Python prend le premier
exceptqui correspond, de haut en bas, et un seul. C’est la même logique queif / elif.
Donc si le parent est écrit en premier, les enfants en dessous ne servent à rien :
s = {"name": "web-1"}
try:
print(s["owner"])
except Exception:
print("erreur")
except KeyError:
print("clé absente")
erreur
La règle : du plus précis au plus général.
s = {"name": "web-1"}
try:
print(s["owner"])
except KeyError:
print("clé absente")
except Exception:
print("erreur")
clé absente
Contexte, pas à apprendre. En vrai,
KeyErrora un parent intermédiaire,LookupError, entre lui etException. Ça ne change rien aux deux règles.
À retenir
- Un traceback se lit de bas en haut : la dernière ligne donne le type et le message.
- Dès qu’une exception part, le reste du
tryest sauté. - Un
exceptn’attrape que son type et ses enfants. Sinon, le programme s’arrête. else: seulement s’il n’y a eu aucune exception.finally: toujours.- Les
exceptse rangent du plus précis au plus général :except Exceptionen dernier. ValueError: bon type, mauvaise valeur.TypeError: mauvais type.KeyError: clé absente.FileNotFoundError: fichier introuvable.
Entraînement
12 questions, dont 7 à compléter. Tu réponds, on passe à la suivante. La note arrive à la fin.
Ta note
Cartes · 10
Réviser les cartes ici
Pourquoi