Un programme réel rencontre des imprévus : fichier manquant, saisie invalide, réseau coupé, division par zéro… La gestion des exceptions est le mécanisme Python pour réagir proprement à ces situations sans planter. C’est un savoir-faire essentiel dès le niveau Avancé du TOSA.
35.1 Qu’est-ce qu’une exception ?
Quand Python rencontre une erreur à l’exécution, il « lève » (raises) une exception. Sans gestion, le programme s’arrête immédiatement.
n =int("abc") # ValueErrorprint("Suite...") # jamais atteint
---------------------------------------------------------------------------ValueError Traceback (most recent call last)
CellIn[1], line 1----> 1 n = int("abc") # ValueError 2 print("Suite...") # jamais atteintValueError: invalid literal for int() with base 10: 'abc'
Chaque erreur appartient à un type (ValueError, TypeError, ZeroDivisionError…) et contient un message.
Les exceptions les plus courantes
Exception
Cause typique
SyntaxError
Erreur de syntaxe (détectée avant exécution)
NameError
Variable non définie
TypeError
Opération entre types incompatibles
ValueError
Type correct, valeur invalide (int("abc"))
ZeroDivisionError
Division par zéro
IndexError
Indice hors limites dans une séquence
KeyError
Clé absente dans un dict
FileNotFoundError
Fichier introuvable
AttributeError
Attribut inexistant sur un objet
ImportError / ModuleNotFoundError
Module introuvable
35.2try / except : intercepter une exception
La structure de base :
try:# code risquéexcept TypeErreur:# que faire si TypeErreur arrive
Premier exemple
try: n =int("abc")exceptValueError:print("Ce n'est pas un entier valide !")print("Le programme continue.")
Ce n'est pas un entier valide !
Le programme continue.
Le programme ne plante pas. Python saute dans le except, puis reprend son cours.
Récupérer le message de l’erreur
Avec as e on récupère l’objet exception, notamment pour accéder à son message.
try: n =int("abc")exceptValueErroras e:print(f"Erreur : {e}")
Erreur : invalid literal for int() with base 10: 'abc'
Attraper plusieurs types différents
valeurs = ["10", "abc", None]for v in valeurs:try: n =int(v)print(f"OK : {n}")exceptValueError:print(f"Valeur invalide : {v!r}")exceptTypeError:print(f"Type invalide : {v!r}")
OK : 10
Valeur invalide : 'abc'
Type invalide : None
On peut aussi grouper plusieurs exceptions en un seul except avec un tuple :
valeurs = ["10", "abc", None]for v in valeurs:try: n =int(v)print(f"OK : {n}")except (ValueError, TypeError) as e:print(f"Problème avec {v!r} : {e}")
OK : 10
Problème avec 'abc' : invalid literal for int() with base 10: 'abc'
Problème avec None : int() argument must be a string, a bytes-like object or a real number, not 'NoneType'
⚠️ Évitez except: tout seul
L’exception « attrape-tout » (except: sans type) ou except Exception: attrape même les bugs et masque les vrais problèmes.
# ❌ Mauvaise pratiquetry: traiter()except: # attrape même Ctrl+C !pass
Règle d’or : soyez spécifique. Attrapez les exceptions que vous savez traiter. Laissez les autres remonter (c’est ainsi qu’on découvre les bugs).
35.3 Le pattern EAFP vs LBYL
Python a une philosophie particulière : « il est plus facile de demander pardon que la permission » (Easier to Ask Forgiveness than Permission, EAFP).
LBYL : « Look Before You Leap »
Vérifier avant d’agir — typique des autres langages.
d = {"a": 1, "b": 2}# LBYL : on vérifie avantif"c"in d:print(d["c"])else:print("Clé absente")
Clé absente
EAFP : « Easier to Ask Forgiveness »
Essayer, et rattraper en cas d’erreur.
d = {"a": 1, "b": 2}# EAFP : on tente, on rattrapetry:print(d["c"])exceptKeyError:print("Clé absente")
Clé absente
Laquelle préférer ?
LBYL : plus lisible pour des cas simples, idiomatique avec dict.get().
EAFP : plus sûr dans des environnements concurrents (pas de « race condition » entre le test et l’action), et généralement plus Pythonique.
Pour les dicts, d.get("c", "défaut") est souvent le plus concis.
35.4else et finally
Un bloc try peut inclure jusqu’à 4 clauses : try, except, else, finally.
else — exécuté si aucune exception n’a été levée
def diviser(a, b):try: resultat = a / bexceptZeroDivisionError:print("Division par zéro !")else:print(f"Résultat : {resultat}")diviser(10, 2) # else exécutédiviser(10, 0) # except exécuté, else non
Résultat : 5.0
Division par zéro !
Pourquoi un else ? Pour séparer clairement ce qu’on protège (dans try) de ce qu’on fait ensuite (dans else). Évite d’attraper accidentellement une exception venue du code post-opération.
finally — toujours exécuté
def diviser(a, b):try: resultat = a / bexceptZeroDivisionError:print("Division par zéro")returnNoneelse:return resultatfinally:print("Nettoyage (toujours exécuté)")print(diviser(10, 2))print("---")print(diviser(10, 0))
finally sert au nettoyage : fermer un fichier, libérer une ressource, déconnecter une base de données… Il s’exécute quoi qu’il arrive, même en cas de return ou d’exception non attrapée.
Structure complète
try:# code risquéexcept TypeErreur1:# gestion spécifiqueexcept TypeErreur2:# autre gestionelse:# si aucune exception n'est levéefinally:# toujours exécuté
35.5 Lever une exception : raise
Vous pouvez vous-même lever une exception avec raise.
def diviser(a, b):if b ==0:raiseValueError("Diviseur nul")return a / btry:print(diviser(10, 0))exceptValueErroras e:print(f"Erreur : {e}")
Erreur : Diviseur nul
Quand raise est-il utile ?
Pour signaler un cas invalide dès qu’il est détecté.
Dans des fonctions de validation.
Dans des setters @property (vu au chapitre précédent).
Pour re-lever une exception après l’avoir loguée.
def age_valide(age):ifnotisinstance(age, int):raiseTypeError("L'âge doit être un entier")if age <0:raiseValueError("L'âge ne peut pas être négatif")if age >150:raiseValueError("L'âge est irréaliste")return age# Teststry: age_valide(-5)exceptValueErroras e:print(f"Refusé : {e}")try: age_valide("vingt")exceptTypeErroras e:print(f"Refusé : {e}")print(age_valide(30)) # OK
Refusé : L'âge ne peut pas être négatif
Refusé : L'âge doit être un entier
30
Re-lever une exception
Parfois, on veut traiter une exception (logger, nettoyer) puis la re-lever pour que le code appelant la gère aussi.
Expressivité : SoldeInsuffisantError est plus clair que Exception ou ValueError.
Granularité : on peut rattraper spécifiquement ses propres erreurs, sans confondre avec d’autres.
Documentation : le nom de l’exception raconte le bug.
Convention de nommage
Les classes d’exception doivent finir par Error (ou Exception). C’est la convention PEP 8 :
✅ SoldeInsuffisantError, UtilisateurInconnuError
❌ SoldeInsuffisant (ambigu)
35.7 Le context manager with — aperçu
Pour garantir que les ressources sont proprement libérées (même en cas d’erreur), Python propose l’instruction with.
withopen("fichier.txt") as f: contenu = f.read()# ... traitements# Le fichier est AUTOMATIQUEMENT fermé à la sortie du bloc,# même si une exception a été levée dans le bloc.
Équivalent (verbeux) avec try/finally :
f =open("fichier.txt")try: contenu = f.read()# ...finally: f.close()
with est toujours préféré pour les fichiers, connexions, verrous, etc. Nous le verrons en détail au chapitre suivant sur les fichiers.
35.8 Arbre d’héritage des exceptions
Les exceptions forment une hiérarchie. En rattraper une rattrape aussi toutes ses sous-classes.
try: d = {"a": 1} d["b"]exceptLookupErroras e: # attrape KeyError ET IndexErrorprint(f"Erreur de lookup : {e}")
Erreur de lookup : 'b'
try:1/0exceptExceptionas e: # attrape PRESQUE toutprint(f"Quelque chose s'est mal passé : {e}")
Quelque chose s'est mal passé : division by zero
Ne pas confondre Exception et BaseException
Exception : toutes les erreurs « normales ». À attraper si besoin.
BaseException : inclut aussi KeyboardInterrupt (Ctrl+C) et SystemExit. Ne l’attrapez jamais sauf raison très précise.
35.9 Assertions : assert
assert est un outil de vérification interne au programme. Si la condition est fausse, une AssertionError est levée.
def moyenne(notes):assertlen(notes) >0, "La liste de notes ne peut pas être vide"returnsum(notes) /len(notes)print(moyenne([12, 15, 14]))
13.666666666666666
moyenne([])
---------------------------------------------------------------------------AssertionError Traceback (most recent call last)
CellIn[17], line 1----> 1 moyenne([])
CellIn[16], line 2, in moyenne(notes) 1def moyenne(notes):
----> 2assert len(notes) > 0, "La liste de notes ne peut pas être vide" 3return sum(notes) / len(notes)
AssertionError: La liste de notes ne peut pas être vide
assert n’est PAS pour la validation d’entrées
Les assertions peuvent être désactivées avec l’option -O (optimize) de Python. Ne les utilisez que pour des vérifications internes que vous êtes sûrs d’être toujours vraies.
def retirer(montant, solde):if montant <=0:raiseValueError("Montant invalide") # ✅ toujours actif
🧩 Quiz 6.1 — Gestion des exceptions
Question 1
Quelle exception est levée par int("abc") ?
TypeError
ValueError
SyntaxError
NameError
🔍 Réponse
b) ValueError — le type (str) est accepté, mais la valeur ne représente pas un entier. TypeError serait pour int([1, 2]) (mauvais type).
int("abc")
---------------------------------------------------------------------------ValueError Traceback (most recent call last)
CellIn[18], line 1----> 1 int("abc")
ValueError: invalid literal for int() with base 10: 'abc'
Question 2
Quelle clause s’exécute si aucune exception n’est levée dans try ?
except
else
finally
raise
🔍 Réponse
b) else — exécuté uniquement si le bloc try s’est déroulé sans aucune exception.
try: x =10exceptException:print("erreur")else:print("OK")
OK
Question 3
Quelle clause s’exécute quoi qu’il arrive ?
try
except
else
finally
🔍 Réponse
d) finally — toujours exécuté, même en cas de return anticipé ou d’exception non rattrapée. Idéal pour le nettoyage.
Question 4
Que s’affiche-t-il ?
try: x =10/0exceptZeroDivisionError: x =0print(x)
10
0
Une erreur
None
🔍 Réponse
b) 0 — la division lève ZeroDivisionError, qui est attrapée, x prend la valeur 0.
try: x =10/0exceptZeroDivisionError: x =0print(x)
0
Question 5
Que fait raise ValueError("msg") ?
Affiche le message
Arrête le programme
Lève une exception ValueError avec le message
Définit une variable nommée ValueError
🔍 Réponse
c) — raise lève une exception. Si elle n’est pas rattrapée, le programme s’arrête ; si elle est dans un try/except, elle peut être gérée.
Question 6
Comment attraper à la fois ValueError et TypeError ?
except ValueError and TypeError:
except ValueError + TypeError:
except (ValueError, TypeError):
except ValueError or TypeError:
🔍 Réponse
c) except (ValueError, TypeError): — tuple d’exceptions entre parenthèses.
Question 7
Quelle est la différence entre Exception et BaseException ?
Aucune, ce sont des synonymes
BaseException inclut KeyboardInterrupt et SystemExit, pas Exception
Exception est plus large
BaseException n’existe pas
🔍 Réponse
b) — BaseException est la racine de la hiérarchie. Attraper BaseException empêcherait notamment l’utilisateur de quitter avec Ctrl+C. Utilisez Exception pour les rattrapages larges.
Question 8
Pourquoi except: tout seul (sans type) est déconseillé ?
C’est une erreur de syntaxe
Il attrape même les bugs que vous ne pouvez pas gérer et les masque
Il ne fonctionne qu’en Python 2
Il est plus lent
🔍 Réponse
b) — except: attrape toutes les exceptions, y compris KeyboardInterrupt, SystemExit, et les bugs de programmation. Cela masque les vrais problèmes et rend le débogage très difficile. Soyez spécifique dans vos except.
✏️ Exercice 6.1 — Conversion sécurisée
Écrivez une fonction convertir_int(chaine, defaut=0) qui convertit une chaîne en entier, et renvoie defaut si la conversion échoue.
def demander_note():"""Demande une note entière entre 0 et 20, redemande si invalide."""whileTrue: saisie =input("Note (0-20) : ")try: note =int(saisie)exceptValueError:print(" Ce n'est pas un entier, réessayez.")continueif0<= note <=20:return noteprint(" La note doit être entre 0 et 20, réessayez.")# Démo interactive : note = demander_note()
def verifier_note(saisie):"""Version testable sans input()."""try: note =int(saisie)exceptValueError:returnNone, "Pas un entier"ifnot0<= note <=20:returnNone, "Hors bornes"return note, "OK"for s in ["abc", "-5", "21", "12", "20"]: note, msg = verifier_note(s)print(f"'{s}' → {note} ({msg})")
Créez une classe AgeInvalideError et une fonction calculer_annee_naissance(age) qui lève cette exception si l’âge est négatif ou > 150. Sinon, renvoie l’année de naissance (année courante - âge).
from datetime import dateclass AgeInvalideError(Exception): ...def calculer_annee_naissance(age): ...# Teststry:print(calculer_annee_naissance(30))print(calculer_annee_naissance(-5))except AgeInvalideError as e:print(f"Erreur : {e}")
from datetime import dateclass AgeInvalideError(Exception):"""Levée quand un âge est hors de la plage plausible."""passdef calculer_annee_naissance(age):if age <0:raise AgeInvalideError(f"Âge négatif : {age}")if age >150:raise AgeInvalideError(f"Âge irréaliste : {age}")return date.today().year - age# Testsfor age in [30, -5, 200, 80]:try: an = calculer_annee_naissance(age)print(f" Âge {age} → né en {an}")except AgeInvalideError as e:print(f" Refus : {e}")
Âge 30 → né en 1996
Refus : Âge négatif : -5
Refus : Âge irréaliste : 200
Âge 80 → né en 1946
✏️ Exercice 6.4 — finally pour garantir un nettoyage
Simulez une connexion à une base de données avec try/finally. Même en cas d’erreur pendant le traitement, la connexion doit être « fermée ».
def traiter_avec_connexion(donnees):print("Connexion à la BD...") connecte =Truetry:# Traitement potentiellement risqué ...finally:# Fermer la connexion — TOUJOURS ...traiter_avec_connexion([1, 2, 3])traiter_avec_connexion([]) # doit quand même fermer
def traiter_avec_connexion(donnees):print(" → Connexion à la BD établie")try:ifnot donnees:raiseValueError("Aucune donnée à traiter") total =sum(donnees)print(f" → Total calculé : {total}")exceptValueErroras e:print(f" ⚠️ Erreur de traitement : {e}")finally:print(" ← Connexion à la BD fermée")print("Cas 1 : données OK")traiter_avec_connexion([10, 20, 30])print("\nCas 2 : données vides")traiter_avec_connexion([])
Cas 1 : données OK
→ Connexion à la BD établie
→ Total calculé : 60
← Connexion à la BD fermée
Cas 2 : données vides
→ Connexion à la BD établie
⚠️ Erreur de traitement : Aucune donnée à traiter
← Connexion à la BD fermée
Dans le cas 2, même si une exception a eu lieu, finally s’est bien exécuté et a fermé la connexion.
En Python moderne, on préfère with pour ce genre de gestion (vu au chapitre suivant) :
with get_connexion() as conn: conn.traiter(donnees)# Fermeture automatique, même en cas d'exception
À retenir
Points clés du chapitre
Exceptions : mécanisme Python pour signaler/gérer des situations anormales sans planter.
try / except : code risqué / gestion d’erreur. except SpecificError as e: pour accéder au message.
Soyez spécifique dans vos except — ne rattrapez pas Exception sans raison, jamais BaseException.
else : exécuté si aucune exception. finally : toujours exécuté (nettoyage).
raise pour lever vos propres exceptions. Convention : nom finissant par Error.
EAFP (try/except) est souvent préféré à LBYL (if then) en Python.
Créez des exceptions personnalisées pour expressivité — héritez de Exception.
assert sert aux vérifications internes (pas à la validation d’entrées).
with (chapitre suivant) est la manière moderne de garantir le nettoyage.