Les Exceptions en Python
Qu'est-ce qu'une exception ?
Une exception est une erreur qui survient pendant l'execution du programme (erreur dite "runtime"). Sans gestion des exceptions, le programme s'arrete brutalement et affiche un message d'erreur.
# Sans gestion d'exception
nombre = int("abc") # ValueError : l'execution s'arrete ici
print("Cette ligne ne s'execute jamais")
ValueError: invalid literal for int() with base 10: 'abc'
Les exceptions permettent de detecter et de gerer ces situations d'erreur de facon controlee, sans planter le programme.
Les exceptions integrees courantes
Python dispose de nombreuses exceptions predefinies :
| Exception | Quand elle se produit |
|---|---|
ValueError | Valeur incorrecte (ex: int("abc")) |
TypeError | Type incorrect (ex: "2" + 2) |
IndexError | Index hors limites (ex: liste[100]) |
KeyError | Cle absente d'un dict (ex: d["inexistant"]) |
FileNotFoundError | Fichier introuvable |
ZeroDivisionError | Division par zero |
AttributeError | Attribut ou methode inexistant |
ImportError | Import echoue |
NameError | Variable non definie |
StopIteration | Fin d'un iterateur |
# Exemples de chaque exception
liste = [1, 2, 3]
print(liste[10]) # IndexError
dico = {'a': 1}
print(dico['z']) # KeyError
print(10 / 0) # ZeroDivisionError
print("2" + 2) # TypeError
objet = None
objet.attribut # AttributeError
Bloc try/except : capturer les exceptions
La structure de base pour gerer les exceptions est le bloc try/except :
try:
# Code susceptible de provoquer une exception
resultat = 10 / 0
except ZeroDivisionError:
# Ce bloc s'execute si ZeroDivisionError se produit
print("Division par zero impossible !")
# Exemple pratique : lecture d'un entier
def lire_entier(message):
try:
valeur = int(input(message))
return valeur
except ValueError:
print("Ce n'est pas un nombre entier valide.")
return None
age = lire_entier("Entrez votre age : ")
Capturer l'objet exception : except ExceptionType as e:
Pour acceder aux details de l'erreur, utilisez as e :
try:
fichier = open("inexistant.txt", "r")
except FileNotFoundError as e:
print(f"Erreur : {e}")
print(f"Type : {type(e).__name__}")
# Affiche :
# Erreur : [Errno 2] No such file or directory: 'inexistant.txt'
# Type : FileNotFoundError
Plusieurs clauses except
On peut gerer differents types d'exceptions differemment :
def diviser(a, b):
try:
resultat = a / b
return resultat
except ZeroDivisionError:
print("Erreur : division par zero")
return None
except TypeError as e:
print(f"Erreur de type : {e}")
return None
print(diviser(10, 2)) # 5.0
print(diviser(10, 0)) # Erreur : division par zero => None
print(diviser(10, "a")) # Erreur de type : ... => None
Capturer plusieurs exceptions ensemble : except (Type1, Type2):
def convertir_et_diviser(valeur_str, diviseur_str):
try:
valeur = int(valeur_str)
diviseur = int(diviseur_str)
return valeur / diviseur
except (ValueError, ZeroDivisionError) as e:
print(f"Donnees invalides : {e}")
return None
print(convertir_et_diviser("10", "2")) # 5.0
print(convertir_et_diviser("abc", "2")) # Donnees invalides : ...
print(convertir_et_diviser("10", "0")) # Donnees invalides : ...
La clause else : si aucune exception
La clause else s'execute uniquement si aucune exception n'a ete levee dans le try :
def lire_fichier(nom_fichier):
try:
f = open(nom_fichier, 'r')
except FileNotFoundError:
print(f"Le fichier {nom_fichier} est introuvable.")
else:
# Pas d'exception : le fichier a ete ouvert avec succes
contenu = f.read()
f.close()
print(f"Fichier lu : {len(contenu)} caracteres")
return contenu
La clause finally : execution garantie
La clause finally s'execute toujours, qu'il y ait eu une exception ou non. Elle sert typiquement au nettoyage (fermer un fichier, une connexion...).
def traiter_fichier(nom):
fichier = None
try:
fichier = open(nom, 'r')
contenu = fichier.read()
return contenu
except FileNotFoundError:
print("Fichier introuvable")
return None
except PermissionError:
print("Permission refusee")
return None
finally:
# Ce bloc s'execute TOUJOURS, meme si on a fait un return dans try/except
if fichier:
fichier.close()
print("Fichier ferme.")
La structure complete est donc : try → except → else → finally.
Lever une exception : raise
On peut lever (declencher) deliberement une exception avec raise :
def calculer_racine(n):
if n < 0:
raise ValueError(f"Impossible de calculer la racine de {n} : nombre negatif")
import math
return math.sqrt(n)
try:
print(calculer_racine(16)) # 4.0
print(calculer_racine(-4)) # Leve ValueError
except ValueError as e:
print(f"Erreur : {e}")
def valider_age(age):
if not isinstance(age, int):
raise TypeError(f"L'age doit etre un entier, pas {type(age).__name__}")
if age < 0 or age > 150:
raise ValueError(f"L'age {age} n'est pas valide")
return age
Exceptions personnalisees
Vous pouvez creer vos propres exceptions en heritant de Exception :
class ErreurApplication(Exception):
"""Exception de base pour notre application."""
pass
class ErreurAuthentification(ErreurApplication):
"""Echec de l'authentification."""
def __init__(self, nom_utilisateur):
self.nom_utilisateur = nom_utilisateur
super().__init__(f"Authentification echouee pour : {nom_utilisateur}")
class ErreurSoldeInsuffisant(ErreurApplication):
"""Solde insuffisant pour effectuer l'operation."""
def __init__(self, solde, montant):
self.solde = solde
self.montant = montant
super().__init__(
f"Solde insuffisant : {solde} disponible, {montant} requis"
)
def retirer(solde, montant):
if montant > solde:
raise ErreurSoldeInsuffisant(solde, montant)
return solde - montant
try:
nouveau_solde = retirer(100, 150)
except ErreurSoldeInsuffisant as e:
print(f"Erreur : {e}")
print(f"Il vous manque {e.montant - e.solde} euros")
Chaining d'exceptions : raise ... from
Quand on re-leve une exception depuis un handler, on peut conserver le contexte original :
class ErreurBase(Exception):
pass
def charger_config(chemin):
try:
with open(chemin) as f:
import json
return json.load(f)
except FileNotFoundError as e:
raise ErreurBase(f"Configuration introuvable : {chemin}") from e
except json.JSONDecodeError as e:
raise ErreurBase(f"Configuration JSON invalide : {chemin}") from e
try:
config = charger_config("config.json")
except ErreurBase as e:
print(f"Erreur : {e}")
print(f"Cause originale : {e.__cause__}")
raise NewError(...) from original_error lie les deux exceptions. raise NewError(...) seul utilise __context__ implicitement.
Context managers et exceptions
Les context managers (with statement) gerent automatiquement les ressources et appellent le nettoyage meme en cas d'exception :
# Sans context manager : risque de ne pas fermer le fichier
try:
f = open("fichier.txt", "r")
contenu = f.read()
f.close()
except FileNotFoundError:
print("Fichier introuvable")
# Avec context manager : fermeture garantie
try:
with open("fichier.txt", "r") as f:
contenu = f.read()
# f est ferme automatiquement ici, meme en cas d'exception
except FileNotFoundError:
print("Fichier introuvable")
Bonnes pratiques
Soyez specifique dans les exceptions capturees
# Mauvais : trop large, masque les bugs
try:
faire_quelque_chose()
except Exception:
pass # Les bugs disparaissent silencieusement !
# Bon : specifique et informatif
try:
valeur = int(saisie_utilisateur)
except ValueError:
print("Veuillez entrer un nombre entier")
Ne silenciez pas les erreurs
# Mauvais
try:
resultat = risque()
except Exception:
pass # On ignore l'erreur... tres dangereux
# Acceptable (avec log)
import logging
try:
resultat = risque()
except ValueError as e:
logging.warning(f"Valeur inattendue : {e}")
resultat = valeur_par_defaut
Levez des exceptions au bon niveau
# La validation leve une exception
def valider_email(email):
if '@' not in email:
raise ValueError(f"Email invalide : {email}")
return email
# L'appelant gere l'erreur au bon endroit
def inscrire_utilisateur(email, nom):
try:
email_valide = valider_email(email)
except ValueError as e:
print(f"Inscription impossible : {e}")
return False
# ... continuer l'inscription
return True
Exercices pratiques
Exercice 1 : try/except pour une division par zero
Mettez le moins de code possible dans le bloc try. Un bloc try enorme rend difficile l'identification de quelle ligne a provoque l'exception. Isolez precisement les instructions susceptibles d'echouer.
Exercice 2 : try/except/else/finally
Utilisez else pour le code qui ne doit s'executer que si le try a reussi (cela clarifie l'intention). Utilisez finally pour le nettoyage de ressources. En pratique, le context manager with remplace souvent finally pour la gestion de fichiers et connexions.
Exercice 3 : Lever une ValueError avec message personnalise
Incluez toujours une description claire dans le message de votre exception, et si possible la valeur problematique. raise ValueError(f"Age invalide : {age}") est bien plus utile que raise ValueError("Valeur invalide"). Le developpeur qui debug votre code vous remerciera.
Exercice 4 : Creer une exception personnalisee
Creez une exception de base pour votre application (ex: ErreurApp) et faites heriter toutes vos exceptions specifiques. Cela permet aux utilisateurs de capturer soit une exception precise, soit toutes les exceptions de votre app avec except ErreurApp. C'est le meme principe que IOError et FileNotFoundError dans la bibliotheque standard.
Exercice 5 : Gerer plusieurs types d'exceptions
Regroupez les exceptions avec except (Type1, Type2) quand le traitement est identique pour les deux. Si le traitement differe, utilisez deux clauses except separees. Ne regroupez jamais des exceptions tres differentes juste pour reduire le nombre de lignes.
Quiz de revision
Une solution
Vous devez être connecté pour voir le contenu.