La gestion des exceptions en langage Python#
En informatique, même un code rédigé sans aucune erreur de syntaxe peut subir des anomalies imprévues lors de son exécution : un utilisateur qui saisit des lettres à la place d'un âge, un fichier manquant sur le disque dur, un réseau défaillant ou une tentative de division par zéro.
Sans mécanisme de protection, ces anomalies provoquent l'interruption brutale du programme (crash) et affichent une trace d'erreur (Traceback) intimidante pour l'utilisateur. En Python, la gestion professionnelle des erreurs s'effectue via le mécanisme des exceptions.
1. La philosophie Python : EAFP contre LBYL#
Dans la plupart des langages traditionnels (comme le C), on adopte l'approche LBYL (Look Before You Leap — « Regarde avant de sauter ») en multipliant les vérifications préventives préalables avec des if.
En Python, la communauté privilégie la philosophie EAFP (Easier to Ask for Forgiveness than Permission — « Il est plus facile de demander pardon que la permission »). On tente d'exécuter l'action directement dans un bloc try, et si un incident survient, on l'intercepte proprement dans un bloc except.
# Approche LBYL (Vérification préalable lourde) :
if saisie.isdigit():
nombre = int(saisie)
else:
print("Saisie non numerique.")
# Approche EAFP (Recommandée et Pythonique) :
try:
nombre = int(saisie)
except ValueError:
print("Saisie non numerique.")
2. La structure complète : try, except, else, finally#
L'instruction de gestion des exceptions peut comporter jusqu'à quatre clauses complémentaires :
flowchart TD
A[Bloc try : Code à exécuter] -->|Erreur levée| B[Bloc except : Traitement de l'erreur]
A -->|Aucune erreur| C[Bloc else : Traitement nominal]
B --> D[Bloc finally : Nettoyage systématique]
C --> D
| Bloc | Déclenchement | Rôle |
|---|---|---|
try |
Toujours | Encapsule les instructions susceptibles de lever une exception |
except ExceptionType as err |
Si l'erreur ciblée se produit | Intercepte l'erreur, empêche le plantage et traite l'anomalie |
else |
Seulement si aucune erreur n'est survenue dans try |
Exécute la suite du traitement nominal en toute sécurité |
finally |
TOUJOURS (qu'il y ait eu erreur ou non) | Libère les ressources (fermer un fichier, déconnecter une base) |
Exemple complet#
def diviser_nombres(a_str: str, b_str: str) -> None:
try:
a = float(a_str)
b = float(b_str)
resultat = a / b
except ValueError as err:
print(f"Erreur de format : Vous devez saisir des nombres valides ({err}).")
except ZeroDivisionError:
print("Erreur arithmetique : La division par zero est impossible.")
else:
print(f"Succes : {a} / {b} = {resultat:.4f}")
finally:
print("Fin de l'operation de calcul.\n")
# Tests de différents scénarios :
diviser_nombres("10", "2") # Cas nominal -> try, else, finally
diviser_nombres("10", "0") # ZeroDivisionError -> try, except, finally
diviser_nombres("dix", "2") # ValueError -> try, except, finally
3. Les exceptions courantes de la bibliothèque standard#
Toutes les exceptions en Python dérivent de la classe de base BaseException. Voici les plus fréquentes que vous rencontrerez quotidiennement :
| Exception | Cause courante | Exemple déclencheur |
|---|---|---|
ValueError |
Valeur incorrecte passée à une fonction valide | int("bonjour") |
TypeError |
Opération sur des types incompatibles | "Score: " + 10 |
ZeroDivisionError |
Deuxième opérande d'une division égal à zéro | 42 / 0 |
IndexError |
Tentative d'accès à un indice hors des limites d'une liste | [1, 2][5] |
KeyError |
Clé inexistante demandée dans un dictionnaire | {"nom": "Bob"}["age"] |
FileNotFoundError |
Tentative d'ouverture d'un fichier inexistant | open("inexistant.txt") |
KeyboardInterrupt |
L'utilisateur appuie sur Ctrl + C dans la console |
Interruption manuelle |
[!CAUTION] Anti-pattern à bannir : N'écrivez JAMAIS de clause
except:vide ou de clause génériqueexcept Exception:sans raison valable !
Une clauseexcept:attrape absolument tout silencieusement, y compris les fautes de frappe dans vos variables et la combinaisonCtrl + C, rendant le débogage de vos applications impossible. Ciblez toujours expressément les exceptions attendues.
4. Déclencher une exception : raise#
Vous pouvez volontairement lever une exception pour signaler qu'une règle métier n'est pas respectée à l'aide du mot-clé raise :
def calculer_racine_carree(nombre: float) -> float:
if nombre < 0:
raise ValueError(f"Impossible de calculer la racine d'un nombre negatif ({nombre}).")
return nombre ** 0.5
try:
print(calculer_racine_carree(-9))
except ValueError as e:
print(f"Alerte : {e}")
5. Créer ses propres classes d'exceptions#
Dans les projets d'entreprise, il est courant de créer des exceptions personnalisées héritant de la classe Exception pour clarifier la logique métier :
class SoldeInsuffisantError(Exception):
"""Exception levee lorsqu'un retrait depasse le solde disponible."""
pass
class CompteBancaire:
def __init__(self, titulaire: str, solde_initial: float):
self.titulaire = titulaire
self.solde = solde_initial
def retirer(self, montant: float) -> None:
if montant > self.solde:
raise SoldeInsuffisantError(
f"Retrait de {montant} EUR refuse pour {self.titulaire} (Solde : {self.solde} EUR)"
)
self.solde -= montant
print(f"Retrait de {montant} EUR effectue. Nouveau solde : {self.solde} EUR")
# Utilisation :
compte = CompteBancaire("Alice", 100.0)
try:
compte.retirer(150.0)
except SoldeInsuffisantError as err:
print(f"Incident bancaire : {err}")
6. Mise en pratique : Saisie numérique sécurisée en boucle#
Voici un patron de conception (design pattern) classique que vous utiliserez dans la quasi-totalité de vos scripts interactifs :
def demander_entier_positif(message_invite: str) -> int:
"""Force l'utilisateur à saisir un entier strictement positif."""
while True:
saisie = input(message_invite).strip()
try:
valeur = int(saisie)
if valeur <= 0:
print("Erreur : La valeur doit etre un entier strictement positif. Reessayez.")
continue
return valeur # Valeur valide : on sort de la boucle et de la fonction
except ValueError:
print(f"Erreur : '{saisie}' n'est pas un nombre entier valide. Reessayez.")
# Test du module :
quantite = demander_entier_positif("Veuillez saisir le nombre d'articles a commander : ")
print(f"Quantite validee avec succes : {quantite} unites.")
7. Défi d'application#
Écrivez une fonction recuperer_element(liste, index, valeur_par_defaut=None) qui tente de retourner liste[index]. Si une exception IndexError ou TypeError survient, la fonction capture l'erreur et retourne valeur_par_defaut sans jamais interrompre le programme.
Commentaires