Pourquoi gérer les exceptions ? De l'erreur qui plante au programme robuste
Tu as sûrement déjà vu ce message redouté : Traceback (most recent call last): ... ZeroDivisionError: division by zero. Ton programme s'arrête net, l'utilisateur est perdu. C'est ça, une exception non gérée. En NSI, et dans la vraie vie d'un développeur, un bon programme n'est pas seulement un programme qui fonctionne quand tout va bien, mais un programme qui sait réagir quand les choses tournent mal. La gestion des exceptions, c'est l'art d'anticiper les problèmes pour offrir une expérience fluide. Imagine une calculatrice qui plante si tu divises par zéro au lieu d'afficher un message clair. C'est la différence entre un script de débutant et un projet professionnel.
La structure de base : try et except
Le bloc try...except est le cœur de la gestion des erreurs. Son principe est simple : tu essaies d'exécuter du code dans le bloc try. Si une erreur survient, Python saute directement dans le bloc except au lieu de planter.
Un exemple concret
Prenons un cas classique : la conversion d'une saisie utilisateur.
# Sans gestion d'erreur
age = int(input("Quel est ton âge ? "))
print(f"Tu as {age} ans.")
# Si l'utilisateur tape "dix-sept", c'est le crash ! ValueError.
Voici la version robuste :
try:
age = int(input("Quel est ton âge ? "))
print(f"Tu as {age} ans.")
except ValueError:
print("Attention ! Tu dois entrer un nombre entier.")
Ici, si la conversion en int échoue, Python exécute le code dans except ValueError. Le programme continue son exécution normalement après.
Capturer plusieurs types d'exceptions
Tu peux gérer différents types d'erreurs de manière spécifique :
try:
numerateur = int(input("Numérateur : "))
denominateur = int(input("Dénominateur : "))
resultat = numerateur / denominateur
print(f"Résultat : {resultat}")
except ValueError:
print("Erreur : tu dois saisir des nombres.")
except ZeroDivisionError:
print("Erreur : division par zéro impossible.")
Tu peux aussi capturer toutes les exceptions (à utiliser avec prudence) avec un except général ou en capturant la classe mère Exception.
Aller plus loin : else et finally
La structure complète offre deux blocs optionnels très puissants : else et finally.
Le bloc else : exécuté seulement en cas de succès
Le code dans else ne s'exécute que si aucune exception n'a été levée dans le try. C'est parfait pour le code qui dépend du succès du bloc try, mais qui ne doit pas lui-même être protégé des mêmes exceptions.
try:
fichier = open("donnees.txt", "r")
contenu = fichier.read()
except FileNotFoundError:
print("Le fichier est introuvable.")
else:
# Ce code n'est exécuté que si l'ouverture a réussi.
print("Fichier lu avec succès.")
print(f"Taille : {len(contenu)} caractères.")
fichier.close() # On ferme le fichier ici.
Le bloc finally : le nettoyage garanti
Le bloc finally est toujours exécuté, qu'une exception ait été levée ou non, et même si un return ou un break intervient. C'est l'endroit idéal pour les actions de « nettoyage » qui doivent absolument avoir lieu, comme fermer un fichier ou une connexion réseau.
try:
fichier = open("config.txt", "r")
# Traitement qui pourrait planter...
donnees = fichier.read()
resultat = int(donnees) * 2
except (FileNotFoundError, ValueError) as e:
print(f"Problème avec le fichier ou son contenu : {e}")
finally:
# Cette ligne sera exécutée dans TOUS les cas.
print("Tentative de fermeture du fichier.")
fichier.close()
Même si une erreur survient pendant le traitement, le fichier sera fermé, évitant ainsi des fuites de ressources.
Lever et créer ses propres exceptions
Parfois, c'est toi qui dois signaler qu'une erreur s'est produite. Pour cela, on utilise le mot-clé raise.
Lever une exception existante
Imaginons une fonction qui calcule la racine carrée d'un nombre, mais seulement pour les nombres positifs dans notre contexte.
def ma_racine_carree(x):
if x < 0:
raise ValueError("Le nombre doit être positif ou nul.")
return x ** 0.5
try:
print(ma_racine_carree(-4))
except ValueError as erreur:
print(erreur) # Affiche : Le nombre doit être positif ou nul.
Créer sa propre classe d'exception
Pour des erreurs métier spécifiques à ton projet NSI, tu peux définir tes propres classes. Il suffit qu'elles héritent (directement ou indirectement) de la classe Exception.
class ErreurNoteInvalide(Exception):
"""Exception levée pour une note en dehors de l'intervalle 0-20."""
pass
def ajouter_note(note):
if not 0 <= note <= 20:
raise ErreurNoteInvalide(f"La note {note} n'est pas entre 0 et 20.")
# ... traitement de la note ...
try:
ajouter_note(25)
except ErreurNoteInvalide as e:
print(f"Erreur de saisie : {e}")
Cela rend ton code beaucoup plus clair et facile à déboguer.
Bonnes pratiques et pièges à éviter
Ne pas abuser de l'except général : except: ou except Exception: sans préciser le type d'erreur peut masquer des bugs inattendus. Attrape des exceptions spécifiques.
Éviter les blocs try vides ou trop larges : Mets dans le try uniquement le code qui peut générer l'erreur que tu veux capturer.
Donner des messages d'erreur utiles : Pour l'utilisateur ou pour toi-même lors du débogage. Utilise as e pour accéder au message de l'exception.
Structure recommandée pour un code propre :
try: Le code à risque.except: Gestion spécifique des erreurs connues.else: Code à exécuter en cas de succès.finally: Code de nettoyage (fermeture de ressources).
En maîtrisant try, except, else et finally, tu fais un pas de géant vers la création de programmes solides et professionnels, une compétence essentielle pour ton projet de NSI et au-delà.
