Aller au contenu principal

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 :

ExceptionQuand elle se produit
ValueErrorValeur incorrecte (ex: int("abc"))
TypeErrorType incorrect (ex: "2" + 2)
IndexErrorIndex hors limites (ex: liste[100])
KeyErrorCle absente d'un dict (ex: d["inexistant"])
FileNotFoundErrorFichier introuvable
ZeroDivisionErrorDivision par zero
AttributeErrorAttribut ou methode inexistant
ImportErrorImport echoue
NameErrorVariable non definie
StopIterationFin 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 : tryexceptelsefinally.


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

Bonne pratique - Bloc try minimal

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

Bonne pratique - Utiliser else et 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

Bonne pratique - Messages d'erreur explicites

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

Bonne pratique - Hierarchie d'exceptions"

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

Bonne pratique - Grouper les exceptions similaires

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


Quelle clause s'execute TOUJOURS, qu'il y ait eu une exception ou non ?


Que fait 'raise ValueError("message")' ?


Quelle exception est levee quand on accede a un index inexistant dans une liste ?


Quand la clause 'else' d'un try/except s'execute-t-elle ?


Comment creer une exception personnalisee en Python ?


Une solution