Exceptions
Progression
#Exceptions
Les exceptions constituent le mécanisme privilégié en Python pour signaler proprement les situations anormales et garantir la libération correcte des ressources. Elles permettent de séparer la logique métier du traitement des erreurs, rendant le code plus lisible et plus robuste. Python offre une structure complète avec try/except/else/finally, des hiérarchies d'exceptions spécifiques, le chaînage d'exceptions, et des context managers pour automatiser le nettoyage des ressources.
Objectifs d'apprentissage
L'objectif est d'apprendre à utiliser correctement les blocs
try/except/else/finallyen fonction de l'intention (gestion d'erreur ou nettoyage de ressources). Vous apprendrez également à lever des exceptions précises et à les chaîner avecraise ... from epour préserver le contexte d'erreur. Enfin, vous maîtriserez l'écriture de context managers avec l'instructionwithpour garantir la libération automatique des ressources.
1try:2 1/03except ZeroDivisionError:4 print('division par zéro !')5finally:6 print('Toujours exécuté')Interceptez des exceptions précises (ex: ValueError, KeyError) et laissez les autres remonter. Évitez except Exception sans re‑raise ciblé.
Lever des exceptions personnalisées permet de communiquer clairement les problèmes métier. Il est important de choisir un type d'exception approprié ou de créer ses propres classes d'exception pour distinguer les différentes catégories d'erreurs :
1def racine(n: float) -> float:2 if n < 0:3 raise ValueError('n doit être >= 0')4 return n ** 0.55 6print(racine(9))Le chaînage d'exceptions avec raise ... from e permet de préserver la trace complète de l'erreur d'origine tout en enveloppant l'exception dans un contexte métier plus significatif. Cette pratique facilite grandement le diagnostic des problèmes en production :
1class PaymentError(Exception):2 pass3 4def pay():5 try:6 1/07 except ZeroDivisionError as e:8 raise PaymentError('échec paiement') from e9 10try:11 pay()12except PaymentError as e:13 print('Business error:', e.__cause__.__class__.__name__)#Playground
#Exercices
Ces exercices vous permettront de pratiquer la gestion robuste des exceptions :
Commencez par écrire un parseur robuste capable de lire des lignes au format a;b;c, ignorant intelligemment les lignes invalides tout en produisant un rapport d'erreurs détaillé pour faciliter le débogage. Ensuite, implémentez un context manager timer() qui mesure automatiquement la durée d'exécution d'un bloc de code lorsqu'il est utilisé avec l'instruction with, démontrant ainsi la puissance de cette abstraction pour la gestion des ressources.
#Pièges fréquents
Plusieurs pièges classiques guettent les développeurs lors de la gestion des exceptions. Utiliser except Exception: de manière trop large peut masquer des bugs réels en interceptant des erreurs inattendues. Il est crucial de ne pas oublier le bloc finally (ou mieux, un context manager) pour garantir la libération des fichiers, verrous ou sockets, même en cas d'erreur. Enfin, utiliser return dans un bloc finally supprime l'exception en cours de propagation, ce qui peut masquer des problèmes graves.
#Danger: return dans finally
1def f():2 try:3 1/04 finally:5 return 'masque l\'exception' # à éviter6 7print(f()) # L'exception ZeroDivisionError est perdue !Règle simple : ne jamais retourner une valeur depuis finally. Effectuez les nettoyages nécessaires, puis laissez l'exception remonter naturellement vers l'appelant.
#Relancer proprement
Lorsque vous interceptez une exception pour ajouter du contexte, utilisez le chaînage avec from pour préserver la trace complète de l'erreur d'origine :
1try:2 ouvrir_fichier()3except OSError as e:4 # Ajouter du contexte sans perdre la trace d'origine5 raise RuntimeError('échec ouverture fichier config') from e#Nettoyages et context managers
Les context managers, utilisés avec l'instruction with, garantissent automatiquement la fermeture ou la libération des ressources, même en cas d'exception. Cette approche est plus sûre et plus lisible que la gestion manuelle dans des blocs finally :
1from contextlib import contextmanager2 3@contextmanager4def timer():5 import time6 t0=time.perf_counter();7 try:8 yield9 finally:10 print(f"{(time.perf_counter()-t0)*1e3:.1f} ms")11 12with timer():13 sum(range(10_000))