La gestion des erreurs est un aspect fondamental de la programmation robuste. En Python, cela se fait principalement via le mécanisme des exceptions, qui permet de signaler et de traiter les conidtions anormales survenues lors de l'exécution d'un programme.
Concepts fondamentaux : erreurs vs exceptions
Une erreur de syntaxe empêche l'interpréteur de comprendre le code et doit être corrigée avant l'exécution. Une erreur logique conduit à un résultat incorrect ou à un comportement imprévu. Une exception, en revanche, survient à l'exécution lorsque Python rencontre une situation qu'il ne peut pas gérer normalement (comme une division par zéro). L'interprète interrompt alors le flux d'exécution normal et recherche un gestionnaire d'exception approprié.
La structure de base : le bloc try-except
Le mécanisme de base pour intercepter et gérer les exceptions est le bloc try...except. Le code susceptible de générer une exception est placé dans le bloc try. Si une exception se produit, l'exécution saute immédiatement au bloc except correspondant au type d'exception levée.
def lire_entier_positif():
while True:
try:
saisie = input("Veuillez entrer un entier positif : ")
valeur = int(saisie)
if valeur <= 0:
raise ValueError("La valeur doit être positive")
return valeur
except ValueError as details:
print(f"Saisie invalide : {details}. Réessayez.")
Cet exemple illustre plusieurs points clés : l'interception d'une exception prédéfinie (ValueError), l'utilisation d'une clause as pour capturer l'objet exception, et la levée explicite d'une exception (raise) avec un message personnalisé pour des contraintes métier.
Précision et flexibilité avec les clauses multiples
On peut gérer différents types d'exceptions dans des blocs except distincts pour appliquer une logique spécifique à chaque situation d'erreur. L'ordre des clauses est important : Python teste les exceptions dans l'ordre où elles sont listées.
def analyser_donnees(chemin_fichier):
try:
with open(chemin_fichier, 'r') as f:
contenu = f.read()
nombre = int(contenu.strip())
resultat = 1000 / nombre
return resultat
except FileNotFoundError as err_fichier:
print(f"ERREUR : Le fichier spécifié est introuvable. ({err_fichier})")
except (ValueError, TypeError):
print("ERREUR : Le contenu du fichier n'est pas un nombre entier valide.")
except ZeroDivisionError:
print("ERREUR : Le nombre contenu dans le fichier est zéro.")
except Exception as e:
print(f"Une erreur inattendue est survenue : {type(e).__name__}")
La clause except Exception sert de filet de sécurité pour toute exception non anticipée. On peut aussi regrouper plusieurs types d'exceptions dans un tuple pour leur appliquer un même traitement.
Assurer le nettoyage : la clause finally
La clause finally définit un bloc de code qui s'exécute systématiquement, qu'une exception ait été levée, interceptée ou non. C'est l'endroit idéal pour libérer des ressources critiques comme la fermeture de fichiers ou de connexions réseau.
connexion = None
try:
connexion = obtenir_connexion_bd()
curseur = connexion.execute("SELECT * FROM donnees_sensibles")
traiter_resultats(curseur)
except ErreurBaseDeDonnees as e:
journal_logger(f"Échec de la requête : {e}")
finally:
if connexion:
connexion.deconnecter()
print("Connexion à la base de données libérée.")
Exécution conditionnelle avec la clause else
La clause else est exécutée seulement si le bloc try s'est terminé sans aucune exception. Cela permet de séparer clairement le code "normal" du code de gestion d'erreur et du code de nettoyage.
def calculer_moyenne(valeurs):
total = 0
compteur = 0
try:
for element in valeurs:
total += float(element)
compteur += 1
except (TypeError, ValueError) as e:
print(f"Impossible de convertir '{element}' en nombre : {e}")
return None
else:
if compteur == 0:
print("Aucune valeur valide fournie.")
return None
return total / compteur
Provoquer des exceptions avec raise
En plus d'intercepter les exceptions, un programme peut en déclencher explicitement avec l'instruction raise. Cela est utile pour signaler une condition d'erreur spécifique dans votre code ou pour propager une exception après avoir effectué une certaine journalisation.
def definir_limite(nouvelle_limite):
if not isinstance(nouvelle_limite, int):
raise TypeError("La limite doit être un entier.")
if nouvelle_limite < 0:
raise ValueError("La limite ne peut pas être négative.")
# ... logique d'application de la limite
Vérifications préalables avec les assertions
L'instruction assert est conçue pour les vérifications de débogage et les invariants de programme. Elle lève une AssertionError si la condition testée est fausse.
def diviser(a, b):
assert isinstance(a, (int, float)), "Le dividende doit être numérique"
assert isinstance(b, (int, float)), "Le diviseur doit être numérique"
assert b != 0, "Division par zéro impossible"
return a / b
Attention : les assertions peuvent être désactivées avec l'option -O de l'interpréteur Python. Ne les utilisez donc pas pour la validation des données provenant de l'utilisateur ou d'une source externe.
Hiérarchie des exceptions intégrées
Toutes les exceptions natives de Python héritent de BaseException. Les exceptions les plus courantes (Exception, ArithmeticError, OSError, etc.) descendent de la sous-classe Exception. Cette hiérarchie permet de capturer des catégories entières d'erreurs. Par exemple, except ArithmeticError interceptera ZeroDivisionError, OverflowError, etc.
Créer des exceptions personnalisées
Pour une gestion plus fine des erreurs dans vos applications, vous pouvez définir vos propres hiérarchies d'exceptions. Il est recommandé de faire hériter vos exceptions personnalisées de Exception.
class ErreurConfiguration(Exception):
"""Classe de base pour les erreurs de configuration."""
pass
class FichierConfigManquant(ErreurConfiguration):
"""Le fichier de configuration principal est absent."""
def __init__(self, chemin):
self.chemin = chemin
super().__init__(f"Fichier de configuration introuvable : {chemin}")
class ValeurConfigInvalide(ErreurConfiguration):
"""Une valeur dans la configuration est incorrecte."""
def __init__(self, cle, valeur, raison):
self.cle = cle
self.valeur = valeur
self.raison = raison
super().__init__(f"Valeur invalide pour '{cle}': '{valeur}' - {raison}")
Diagnostic avancé avec sys.exc_info()
La fonction sys.exc_info() retourne un tuple de trois éléments contenant des informations détaillées sur l'exception en cours de traitement : le type de l'exception, l'instance de l'exception, et un objet traceback décrivant la pile d'appels au moment de l'erreur.
import sys
import traceback
def traiter_erreur_inconnue():
try:
operation_complexe()
except:
type_exc, valeur_exc, tb_objet = sys.exc_info()
print(f"Type : {type_exc.__name__}")
print(f"Valeur : {valeur_exc}")
# On peut parcourir l'objet traceback pour des détails précis
lignes_tb = traceback.format_tb(tb_objet)
print("Traceback :")
for ligne in lignes_tb:
print(ligne.strip())
Exemple pratique : script de conversion robuste
Intégrons ces concepts dans un script concret qui lit un fichier, effectue un calcul et gère proprement les erreurs courantes.
#!/usr/bin/env python3
import sys
import os
def extraire_et_calculer(chemin_entree):
"""Lit un nombre depuis un fichier et calcule son inverse."""
# Validation initiale du chemin
if not os.path.isfile(chemin_entree):
raise FileNotFoundError(f"Fichier non trouvé : {chemin_entree}")
try:
with open(chemin_entree, 'r') as fichier_entree:
contenu = fichier_entree.read().strip()
if not contenu:
raise ValueError("Le fichier est vide.")
nombre = int(contenu)
resultat = 1.0 / nombre
return resultat
except ValueError as e:
# Convertit l'erreur en une exception de notre domaine
raise ErreurTraitement(f"Contenu invalide dans '{chemin_entree}': {e}") from e
except ZeroDivisionError:
raise ErreurTraitement(f"La valeur dans '{chemin_entree}' est zéro.")
except IOError as e:
raise ErreurTraitement(f"Erreur d'E/S sur '{chemin_entree}': {e}") from e
class ErreurTraitement(Exception):
"""Exception spécifique aux erreurs de traitement des données."""
pass
# Point d'entrée principal
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Utilisation : python script.py <fichier_donnees>", file=sys.stderr)
sys.exit(1)
fichier = sys.argv[1]
try:
valeur = extraire_et_calculer(fichier)
print(f"Résultat du calcul : {valeur}")
except (FileNotFoundError, ErreurTraitement) as e:
print(f"ERREUR : {e}", file=sys.stderr)
sys.exit(1)
</fichier_donnees>
Ce script démontre une approche structurée : validation des entrées, conversion des exceptions bas niveau en exceptions métier informatives, et gestion centralisée des erreurs au point d'entrée.