Exceptions en Python : try, except, else, finally, raise et exceptions personnalisées
Une exception en Python est un objet qui signale une erreur et interrompt le déroulement normal du code, jusqu'à ce qu'un bloc try/except l'intercepte ; else s'exécute s'il n'y a pas eu d'erreur, et finally s'exécute toujours. On lève soi-même une exception avec l'instruction raise, par exemple raise ValueError("montant négatif"), et une exception personnalisée est une classe héritant de Exception.
Une exception en Python est un objet qui signale une erreur et interrompt le cours normal du programme. Si personne ne l'intercepte, Python affiche un traceback et s'arrête. On intercepte les exceptions avec la construction try/except, et on les déclenche soi-même avec l'instruction raise. Voyons comment tout cela fonctionne, quelles erreurs sont les plus fréquentes, et comment ne pas transformer la gestion des erreurs en simple dissimulation.
Qu'est-ce qu'une exception en Python
Lorsqu'une opération ne peut pas aboutir — division par zéro, accès à une clé absente, conversion d'un mot en nombre — Python crée un objet d'exception et le « lève ». L'exécution de la fonction en cours s'arrête, l'exception remonte la chaîne d'appels jusqu'à rencontrer un gestionnaire adapté. S'il n'y en a aucun, le programme plante.
>>> 10 / 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
Pour répondre à une question fréquente : une division par zéro en Python provoque l'exception ZeroDivisionError. C'est vrai pour /, pour // et pour %.
Comment lire un traceback
Le traceback est le rapport du chemin qu'a suivi le programme jusqu'à l'erreur. Il se lit de bas en haut. La dernière ligne est l'essentiel : le type de l'exception et son message. Au-dessus, on voit la ligne de code où tout s'est produit, puis qui l'a appelée, et ainsi de suite jusqu'au point d'entrée.
Traceback (most recent call last):
File "suivi.py", line 9, in <module>
total = lire_montant(ligne)
File "suivi.py", line 4, in lire_montant
return float(brut.strip())
~~~~~^^^^^^^^^^^^^
ValueError: could not convert string to float: 'cent'
Depuis Python 3.11, des marqueurs ~~~^^^ sont dessinés sous la ligne de code (PEP 657) : ils montrent précisément quelle expression a échoué. C'est particulièrement utile quand plusieurs appels se suivent sur une même ligne.
Suggestions « Did you mean »
Depuis Python 3.10, en cas de faute de frappe dans un nom, l'erreur propose une variante proche. Dans Python 3.15, ces suggestions sont devenues plus fines : l'interpréteur connaît les habitudes d'autres langages. L'appel list.push suggérera append, et "café".toUpperCase() suggérera la méthode upper. La suggestion regarde aussi dans les attributs imbriqués : si un dictionnaire se trouve dans l'attribut totals d'un objet et que vous appelez items() directement sur l'objet, l'erreur propose .totals.items. Des noms proches sont désormais suggérés aussi pour delattr().
Types d'exceptions en Python
Il existe des dizaines d'exceptions intégrées, mais au début vous rencontrerez surtout celles-ci :
| Exception | Quand elle survient | Exemple |
|---|---|---|
NameError | Le nom n'est pas défini, le plus souvent une faute de frappe | print(totl) |
TypeError | L'opération ne convient pas à ce type | "5" + 3 |
ValueError | Le type est correct, mais pas la valeur | int("cent") |
KeyError | La clé n'existe pas dans le dictionnaire | d["absent"] |
IndexError | Indice hors de la liste | [1, 2][5] |
AttributeError | L'objet n'a pas cette méthode ou ce champ | [].push(1) |
ZeroDivisionError | Division par zéro | 1 / 0 |
FileNotFoundError | Fichier introuvable | open("nope.txt") |
À part, SyntaxError (et son cas particulier IndentationError) survient déjà à l'analyse du fichier, avant l'exécution du code : on ne peut donc pas l'intercepter par try dans le même fichier.
Hiérarchie des exceptions
Les exceptions sont des classes, et elles forment une hiérarchie. Au sommet se trouve BaseException. En dessous, Exception regroupe presque toutes les erreurs « ordinaires », ainsi que quelques exceptions système : KeyboardInterrupt, SystemExit, GeneratorExit.
BaseException
├── KeyboardInterrupt
├── SystemExit
└── Exception
├── ArithmeticError
│ └── ZeroDivisionError
├── LookupError
│ ├── KeyError
│ └── IndexError
├── OSError
│ └── FileNotFoundError
├── TypeError
└── ValueError
La hiérarchie compte au moment de l'interception : except LookupError attrape aussi bien KeyError que IndexError. Quant à KeyboardInterrupt, ce n'est pas une erreur du code : Python signale ainsi que l'utilisateur a appuyé sur Ctrl+C. Elle n'hérite pas de Exception exprès, afin que except Exception n'empêche pas d'interrompre le programme.
Gestion des exceptions : try/except/else/finally
Le code susceptible d'échouer se place dans un bloc try. Le bloc except indique le type d'erreur attendu et ce qu'il faut faire. C'est lui qui assure, en Python, la gestion des exceptions.
def lire_montant(brut):
try:
valeur = float(brut)
except ValueError as e:
print(f"Ligne ignorée : {e}")
return None
else:
return valeur # exécuté seulement si aucune erreur n'a eu lieu
finally:
print("analyse terminée") # exécuté dans tous les cas
print(lire_montant("250"))
Ici, l'affichage commence par « analyse terminée », puis vient 250.0. Le return du bloc else mémorise la valeur, Python exécute ensuite obligatoirement finally, et ne renvoie le résultat qu'après. C'est pourquoi finally est fiable pour le nettoyage : fermer un fichier, libérer une connexion.
Si vous écrivez votre propre return dans finally, il remplace le précédent et peut même « avaler » l'exception. Depuis Python 3.14, un tel code déclenche un SyntaxWarning (PEP 765) : n'écrivez pas de return dans finally.
Plusieurs exceptions à la fois
On peut écrire plusieurs blocs except pour différents types, ou énumérer les types dans un tuple :
try:
montant = int(donnees["montant"])
except KeyError:
montant = 0
except (ValueError, TypeError) as e:
print("Montant incorrect :", e)
montant = 0
Les blocs sont examinés de haut en bas, et le premier qui correspond s'exécute : placez donc les types les plus précis avant les plus généraux. Depuis Python 3.14 (PEP 758), plusieurs types peuvent être énumérés sans parenthèses — except ValueError, TypeError: —, mais si l'on utilise as, les parenthèses restent obligatoires.
Afficher le texte d'une erreur et connaître son type
L'objet d'exception est accessible via as e. str(e) donne le message, type(e).__name__ donne le nom du type :
try:
int("cent")
except ValueError as e:
print(type(e).__name__) # ValueError
print(e) # invalid literal for int() with base 10: 'cent'
Module traceback : le texte complet de l'erreur
Il arrive qu'on doive intercepter une erreur tout en conservant le traceback complet, par exemple pour un journal ou un rapport. Le module traceback sert à cela :
import traceback
try:
1 / 0
except ZeroDivisionError:
traceback.print_exc() # affiche le traceback sur stderr
texte = traceback.format_exc() # le même texte, sous forme de chaîne
Si vous journalisez avec le module logging, il est plus simple d'appeler logging.exception("calcul impossible") dans le except : le traceback est ajouté au message automatiquement.
Python raise : lever une exception
L'instruction raise déclenche une exception manuellement. Une fonction signale ainsi qu'elle ne peut pas traiter certaines données :
def ajouter_depense(montant):
if montant < 0:
raise ValueError(f"Le montant ne peut pas être négatif : {montant}")
...
ajouter_depense(-5)
# ValueError: Le montant ne peut pas être négatif : -5
raise est une instruction et non une fonction : on n'écrit pas de parenthèses après le mot-clé. Les arguments passés entre parenthèses sont ceux du constructeur de l'exception. Choisissez le type selon le sens : une valeur incorrecte relève de ValueError, un type incorrect de TypeError.
raise sans argument et raise from
Dans un except, un raise nu relance l'exception en cours sans la modifier. C'est utile lorsque vous voulez seulement consigner l'erreur, et laisser le traitement au code appelant.
Si vous remplacez une exception par une autre, utilisez raise ... from e : l'erreur d'origine reste dans la chaîne, et le traceback montre les deux :
def charger_config(chemin):
try:
with open(chemin) as f:
return f.read()
except FileNotFoundError as e:
raise RuntimeError(f"Fichier de configuration absent : {chemin}") from e
add_note : ajouter du contexte à l'erreur
Depuis Python 3.11 (PEP 678), on peut ajouter une note à une exception avec la méthode add_note(), puis la relancer. Le type de l'erreur ne change pas, et la note s'affiche sous le message :
def analyser(brut, numero_ligne):
try:
return float(brut)
except ValueError as e:
e.add_note(f"fichier depenses.csv, ligne {numero_ligne}")
raise
analyser("cent", 17)
# ValueError: could not convert string to float: 'cent'
# fichier depenses.csv, ligne 17
Créer sa propre exception en Python
Les exceptions personnalisées servent aux erreurs propres au domaine : budget dépassé, commande déjà payée, utilisateur bloqué. Il suffit d'une classe héritant de Exception :
class BudgetDepasse(Exception):
def __init__(self, categorie, limite, depense):
super().__init__(f"{categorie} : limite {limite}, dépensé {depense}")
self.categorie = categorie
self.limite = limite
self.depense = depense
def verifier_budget(categorie, limite, depense):
if depense > limite:
raise BudgetDepasse(categorie, limite, depense)
try:
verifier_budget("Alimentation", 10000, 12500)
except BudgetDepasse as e:
print("Attention :", e) # Attention : Alimentation : limite 10000, dépensé 12500
Le code appelant peut désormais attraper précisément le dépassement de budget, sans le confondre avec une faute de frappe. Dans un grand projet, il est pratique de créer une classe de base commune (class ErreurSuivi(Exception)) dont héritent toutes vos exceptions : elles pourront alors être interceptées par un seul except ErreurSuivi. Héritez de Exception, et non de BaseException.
Groupes d'exceptions et except*
Il arrive que plusieurs erreurs surviennent en même temps, par exemple lors de la vérification d'un fichier entier ou dans des tâches parallèles de asyncio.TaskGroup. Depuis Python 3.11, on peut les regrouper dans un ExceptionGroup et les traiter par type avec except* :
def verifier_tout(lignes):
erreurs = []
for l in lignes:
try:
float(l)
except ValueError as e:
erreurs.append(e)
if erreurs:
raise ExceptionGroup("erreurs dans le fichier", erreurs)
try:
verifier_tout(["1", "a", "b"])
except* ValueError as eg:
print(f"Lignes incorrectes : {len(eg.exceptions)}") # Lignes incorrectes : 2
Python try except toutes les erreurs : pourquoi c'est une mauvaise idée
L'habitude la plus néfaste est d'attraper tout ce qui passe : except: pass. Le programme « cesse de planter », mais en réalité il cesse de signaler les problèmes. Un except nu intercepte même KeyboardInterrupt et SystemExit, de sorte qu'on ne peut plus arrêter le programme avec Ctrl+C. except Exception: pass est un peu moins grave, mais il avale en silence les fautes de frappe dans les noms et les erreurs de logique.
Comment gérer correctement les exceptions en Python :
- n'attrapez que le type que vous savez traiter :
except ValueError, et non tout ; - gardez le bloc
trycourt : seulement les lignes qui peuvent échouer de cette façon ; - si vous attrapez largement (
except Exception) au niveau le plus haut du programme, journalisez obligatoirement le traceback ; - n'utilisez pas les exceptions pour un simple branchement quand une vérification suffit :
dict.get()plutôt quetry/except KeyErrordans les cas simples ; - pour les ressources, préférez
with, qui ferme le fichier même en cas d'erreur.
En bref
Les exceptions en Python sont un mécanisme normal pour signaler les erreurs, et non une raison de les cacher. Lisez le traceback de bas en haut, interceptez des types précis dans try/except, utilisez else pour le code « si tout s'est bien passé » et finally pour le nettoyage. Python raise permet de signaler soi-même une erreur, raise from et add_note conservent le contexte, et vos exceptions personnalisées héritent de Exception. Ce thème est mis en pratique dans le cours sur Python : le suivi des dépenses cesse de planter à cause d'une ligne CSV erronée, et signale un dépassement de budget par sa propre exception.
Questions fréquentes
Quelle est la différence entre une erreur et une exception en Python ?
Dans le langage courant, ce sont des synonymes. Techniquement, SyntaxError est détectée dès l'analyse du code, tandis que les autres erreurs surviennent à l'exécution sous forme d'objets d'exception que l'on peut intercepter.
Peut-on reprendre l'exécution au même endroit après une exception ?
Non. Après interception, l'exécution continue après le bloc try/except, et non à la ligne qui a échoué. Si vous voulez traiter chaque ligne d'un fichier, placez le try à l'intérieur de la boucle.
Que se passe-t-il si une exception survient à l'intérieur d'un except ?
La nouvelle exception remonte, et l'exception initiale est conservée dans son attribut __context__. Le traceback affiche alors la phrase During handling of the above exception, another exception occurred.
Comment vérifier dans des tests qu'une fonction lève une exception ?
Avec pytest, on utilise with pytest.raises(ValueError):, avec unittest, self.assertRaises(ValueError). Le test ne réussit que si un bloc lève une exception du bon type.
Qu'est-ce que assert et en quoi diffère-t-il de raise ?
assert condition, "message" lève AssertionError si la condition est fausse. C'est un outil de vérification pendant le développement : lancé avec l'option -O, les assert sont désactivés, donc la validation des données saisies par l'utilisateur doit se faire avec if et raise.
Ce sujet est traité en détail avec de la pratique dans le cours « Python 3.15: от новичка до профи ».
Voir le cours