En programmation, les erreurs sont inévitables. Python distingue principalement deux types d'erreurs : les erreurs de syntaxe et les exceptions. Les erreurs de syntaxe surviennent lorsque le code ne respecte pas les règles grammaticales du langage, empêchant le programme de démarrer. Les exceptions, en revanche, surviennent pendant l'exécution d'un programme dont la syntaxe est correcte, signalant un événement inattendu qui perturbe le déroulement normal.
Principes de la Gestion d'Exceptions avec try...except
Le mécanisme principal pour gérer les exceptions en Python est le bloc try...except. Il permet d'envelopper une section de code susceptible de lever une exception et de fournir une manière élégante de réagir si une telle situation se produit, sans que le programme ne plante brutalement.
Exemple Basique
# Tentative d'une opération risquée
try:
valeur_numerique = int("bonjour") # Cette ligne lèvera une ValueError
resultat_division = 10 / valeur_numerique
except ValueError:
print("Erreur : Impossible de convertir la chaîne en nombre entier.")
except ZeroDivisionError:
print("Erreur : La division par zéro n'est pas autorisée.")
print("Le programme continue son exécution après la gestion d'erreur.")
Dans cet exemple, si la conversion en entier échoue (ce qui est le cas pour "bonjour"), le code dans le bloc except ValueError est exécuté. Si, pour une raison quelconque, une division par zéro devait se produire, le bloc except ZeroDivisionError serait déclenché.
Capture Générique d'Exceptions
Il est possible de capturer toutes les exceptions non spécifiées en utilisant un bloc except générique ou en capturant la classe de base Exception.
try:
donnees_utilisateur = {'nom': 'Alice'}
# Tentative d'accès à une clé inexistante ou d'une opération invalide
print(donnees_utilisateur['age']) # Lèvera une KeyError
except KeyError as e:
print(f"Clé manquante : {e}. Veuillez vérifier les données.")
except Exception as e: # Capture toute autre exception inattendue
print(f"Une erreur imprévue est survenue : {e}")
Le mot-clé as e permet d'assigner l'objet exception à une variable (ici e), ce qui est utile pour accéder aux détails du message d'erreur.
Les Clauses else et finally
Les blocs try...except peuvent être complétés par des clauses else et finally pour une gestion plus fine du flux d'exécution.
- Le bloc
elseest exécuté uniquement si aucune exception n'a été levée dans le bloctry. - Le bloc
finallyest toujours exécuté, qu'une exception ait été levée ou non, et qu'elle ait été gérée ou non. Il est idéal pour les opérations de nettoyage (fermeture de fichiers, libération de ressources).
chemin_fichier = "donnees_test.txt"
try:
with open(chemin_fichier, 'r') as fichier:
contenu = fichier.read()
except FileNotFoundError:
print(f"Erreur : Le fichier '{chemin_fichier}' n'existe pas.")
# Création du fichier pour l'exemple suivant si nécessaire
with open(chemin_fichier, 'w') as fichier:
fichier.write("Exemple de contenu.")
print(f"Le fichier '{chemin_fichier}' a été créé avec un contenu par défaut.")
except IOError as e:
print(f"Erreur d'entrée/sortie lors de l'accès au fichier : {e}")
else:
print(f"Lecture du fichier '{chemin_fichier}' réussie. Contenu :")
print(contenu[:50] + "...") # Affiche les 50 premiers caractères
finally:
print("Opération de gestion de fichier terminée.")
# Ici, des ressources comme des connexions de base de données seraient fermées.
Lever des Exceptions Manuellement avec raise
Il est parfois nécessaire de déclencher une exception de manière explicite si une condition d'erreur se produit que votre code détecte. Cela se fait avec l'instruction raise.
def verifier_parametre(valeur, limite_min, limite_max):
if not isinstance(valeur, (int, float)):
raise TypeError("La valeur doit être un nombre.")
if not (limite_min <= valeur <= limite_max):
raise ValueError(f"La valeur {valeur} est hors des limites autorisées [{limite_min}, {limite_max}].")
return True
try:
verifier_parametre("vingt", 0, 100) # Lèvera une TypeError
except TypeError as te:
print(f"Validation échouée : {te}")
try:
verifier_parametre(150, 0, 100) # Lèvera une ValueError
except ValueError as ve:
print(f"Validation échouée : {ve}")
Créer des Exceptions Personnalisées
Pour des scénarios d'erreur spécifiques à votre appliaction, vous pouvez définir vos propres classes d'exception en héritant de la classe de base Exception (ou d'une de ses sous-classes). Cela améliore la clarté et la modularité de la gestion des erreurs.
class ErreurAuthentification(Exception):
"""Exception levée en cas d'échec d'authentification."""
def __init__(self, message="Échec de l'authentification. Identifiants invalides."):
self.message = message
super().__init__(self.message)
def tenter_connexion(utilisateur, mot_de_passe):
if utilisateur != "admin" or mot_de_passe != "secure123":
raise ErreurAuthentification("Nom d'utilisateur ou mot de passe incorrect.")
print(f"Connexion réussie pour l'utilisateur '{utilisateur}'.")
try:
tenter_connexion("admin", "mauvais_mdp")
except ErreurAuthentification as e:
print(f"Problème de connexion : {e}")
try:
tenter_connexion("admin", "secure123")
except ErreurAuthentification as e:
print(f"Problème de connexion : {e}")
Types Courants d'Exceptions Intégrées
Python fournit de nombreuses exceptions intégrées pour couvrir les scénarios d'erreur les plus fréquents. En voici quelques-unes :
IndexError: Un indice de séquence est hors de portée.KeyError: Une clé n'est pas trouvée dans un dictionnaire.NameError: Une variable locale ou globale n'est pas trouvée.TypeError: Une opération est appliquée à un objet de type inapproprié.ValueError: Une fonction reçoit un argument de type correct, mais de valeur inappropriée.AttributeError: Une tentative d'accès à un attribut ou une méthode inexistant sur un objet.FileNotFoundError: Un fichier ou un répertoire est introuvable.ZeroDivisionError: Le deuxième opérande d'une division ou d'un modulo est zéro.ImportError: Un module ou un nom dans un module ne peut pas être importé.IndentationError: Erreur d'indentation du code.
Obtention d'Informations Détaillées sur les Exceptions
Lorsqu'une exception est capturée, il est souvent utile de connaître les détails de l'erreur, tels que le type, le message, et surtout la pile d'appels (traceback) qui indique précisément où l'erreur s'est produite.
import sys
import traceback
def fonction_avec_erreur(donnee):
# Cette fonction peut générer une IndexError
liste_elements = [10, 20, 30]
print(liste_elements[donnee]) # Accès à un indice hors limites
def execute_et_capture():
try:
fonction_avec_erreur(5) # Provoque une IndexError
except Exception as exc:
# sys.exc_info() retourne (type_exc, valeur_exc, objet_traceback)
exc_type, exc_value, tb = sys.exc_info()
# Le traceback object contient des informations sur la pile d'appels
# On peut remonter la pile pour trouver l'origine de l'erreur
frame = tb.tb_frame
nom_fichier = frame.f_code.co_filename
numero_ligne = tb.tb_lineno # Ligne où l'exception s'est produite
print(f"Une exception de type '{exc_type.__name__}' a été capturée : {exc_value}")
print(f"Détails de l'erreur : Fichier '{nom_fichier}', Ligne {numero_ligne}")
print("\nTraceback complète de l'exception :")
# traceback.print_exc() affiche la traceback de manière formatée
traceback.print_exc()
execute_et_capture()
En utilisant sys.exc_info(), nous pouvons obtenir l'objet traceback qui nous permet d'extraire des informations précises comme le nom du fichier et le numéro de ligne où l'exception est survenue. Le module traceback offre des outils pour imprimer ou formater la pile d'appels complète, ce qui est inestimable pour le débogage.