Aller au contenu
Développement medium

Exceptions Python : try, except, raise et finally

18 min de lecture

logo python

La gestion des exceptions en Python permet de traiter les erreurs sans que le programme ne s'arrête brutalement. Le principe : le code risqué va dans un bloc try, la réaction à l'erreur dans un bloc except. Ce guide couvre tout le mécanisme : capturer une ou plusieurs exceptions, les blocs else et finally, lever une erreur avec raise, créer ses exceptions personnalisées et adopter les bons réflexes en production (journaliser, chaîner, ne pas masquer).

Il s'adresse aux débutants qui veulent des scripts robustes et à celles et ceux qui automatisent des tâches où une erreur non gérée casse tout un traitement. Chaque notion est illustrée par un exemple exécutable. Le code a été testé avec Python 3.12.

  • Capturer une ou plusieurs erreurs avec try / except.
  • Utiliser else et finally pour le succès et le nettoyage.
  • Lever vos propres erreurs avec raise, et les chaîner avec raise ... from.
  • Créer des exceptions personnalisées adaptées à votre domaine.
  • Journaliser les erreurs proprement plutôt que de les masquer.

Gérer les exceptions permet d'anticiper les erreurs potentielles et d'éviter que le programme ne se termine brutalement. Sans gestion des exceptions, une erreur non traitée provoque l'arrêt immédiat du programme, souvent avec un message d'erreur peu compréhensible pour l'utilisateur final. En capturant et en traitant les exceptions, il est possible de :

  • Prévenir les plantages du programme.
  • Fournir des messages d'erreur clairs et utiles.
  • Libérer proprement les ressources (fichiers, connexions réseau, etc.).
  • Maintenir le flux normal du programme en cas d'erreurs mineures.

Python dispose de nombreux types d'exceptions intégrées, chacune correspondant à une erreur spécifique. Voici quelques-unes des plus courantes :

  • ZeroDivisionError : Tentative de division par zéro.
  • TypeError : Opération sur un objet d'un type inapproprié.
  • ValueError : Valeur ayant le bon type mais une valeur inappropriée.
  • IndexError : Indice de séquence hors de portée.
  • KeyError : Clé non trouvée dans un dictionnaire.
  • AttributeError : Tentative d'accès à un attribut inexistant d'un objet.
  • ImportError : Échec d'importation d'un module.

Exemples :

# ZeroDivisionError
print(10 / 0) # Provoque une ZeroDivisionError
# TypeError
print('5' + 3) # Provoque une TypeError
# ValueError
int('abc') # Provoque une ValueError
# IndexError
liste = [1, 2, 3]
print(liste[5]) # Provoque une IndexError
# KeyError
dictionnaire = {'a': 1, 'b': 2}
print(dictionnaire['c']) # Provoque une KeyError

Comprendre ces exceptions permet de les anticiper et de les gérer correctement dans le code.

Le bloc try...except permet de capturer les exceptions et de définir une réponse appropriée. Le code susceptible de provoquer une exception est placé dans le bloc try et le traitement de l'erreur dans le bloc except.

Syntaxe de base :

try:
... # Code susceptible de provoquer une exception
except NomDeLException:
... # Code à exécuter en cas d'exception

Exemple :

try:
num = int(input("Entrez un nombre : "))
resultat = 10 / num
print(f"Résultat : {resultat}")
except ZeroDivisionError:
print("Erreur : division par zéro.")
except ValueError:
print("Erreur : veuillez entrer un nombre valide.")

Dans cet exemple :

  • Si l'utilisateur entre zéro, une ZeroDivisionError est levée et gérée.
  • Si l'utilisateur entre une valeur non numérique, une ValueError est levée et gérée.
  • Dans les deux cas, le programme affiche un message d'erreur clair au lieu de se terminer brutalement.

Il est possible de gérer plusieurs exceptions dans un même bloc except en les regroupant dans un tuple. Cela permet de traiter de la même manière plusieurs types d'exceptions.

Exemple :

try:
num = int(input("Entrez un nombre : "))
resultat = 10 / num
except (ZeroDivisionError, ValueError) as e:
print(f"Erreur : {e}")

Ici, si une ZeroDivisionError ou une ValueError est levée, le même bloc except les gère et le message d'erreur associé est affiché.

Le bloc else s'exécute si aucune exception n'est levée dans le bloc try. Il est utile pour exécuter du code qui doit seulement s'exécuter si tout s'est bien passé dans le try.

Exemple :

try:
num = int(input("Entrez un nombre : "))
except ValueError:
print("Erreur : veuillez entrer un nombre valide.")
else:
print(f"Vous avez entré le nombre {num}.")

Dans cet exemple, le message de confirmation n'est affiché que si la conversion en entier s'est déroulée sans erreur.

Le bloc finally est exécuté qu'une exception ait été levée ou non. Il est utile pour effectuer des opérations de nettoyage ou libérer des ressources, comme fermer un fichier ou une connexion.

Exemple :

try:
num = int(input("Entrez un nombre : "))
resultat = 10 / num
except ZeroDivisionError:
print("Erreur : division par zéro.")
except ValueError:
print("Erreur : veuillez entrer un nombre valide.")
finally:
print("Fin de l'opération.")

Ici, le message "Fin de l'opération." est toujours affiché, que le programme ait rencontré une exception ou non.

Il est possible de lever explicitement une exception en utilisant le mot-clé raise. Cela permet de signaler qu'une condition anormale s'est produite et de transférer le contrôle au bloc except approprié.

Exemple :

def verifier_age(age):
if age < 0:
raise ValueError("L'âge ne peut pas être négatif.")
elif age < 18:
print("Vous êtes mineur.")
else:
print("Vous êtes majeur.")
try:
verifier_age(-5)
except ValueError as e:
print(f"Erreur : {e}")

Dans cet exemple, la fonction verifier_age lève une ValueError si l'âge est négatif et le bloc try...except capture cette exception pour afficher un message d'erreur.

Il est possible de définir des exceptions personnalisées en créant des classes qui héritent de la classe Exception. Cela permet de créer des erreurs spécifiques à l'application ou au domaine.

Exemple :

class TemperatureError(Exception):
"""Exception levée lorsque la température est invalide."""
pass
def verifier_temperature(temp):
if temp < -273.15:
raise TemperatureError("La température ne peut pas être inférieure au zéro absolu.")
try:
verifier_temperature(-300)
except TemperatureError as e:
print(f"Erreur : {e}")

Dans cet exemple, une exception personnalisée TemperatureError est créée pour gérer des erreurs liées à la température.

Les assertions permettent de vérifier qu'une condition est vraie pendant l'exécution du programme. Si la condition est fausse, une AssertionError est levée. Elles sont principalement utilisées pendant le développement pour détecter des erreurs de logique.

Exemple :

def calculer_factorielle(n):
assert n >= 0, "Le nombre doit être positif ou nul."
if n == 0:
return 1
else:
return n * calculer_factorielle(n - 1)
print(calculer_factorielle(5)) # Affiche 120
print(calculer_factorielle(-3)) # Provoque une AssertionError

Attention : les assertions peuvent être désactivées lors de l'exécution du programme avec l'option -O, il ne faut donc pas les utiliser pour la gestion des erreurs en production.

Il est possible d'imbriquer des blocs try...except pour gérer des exceptions à différents niveaux.

Exemple :

try:
try:
num = int(input("Entrez un nombre : "))
resultat = 10 / num
except ZeroDivisionError:
print("Erreur : division par zéro.")
else:
print(f"Résultat : {resultat}")
except ValueError:
print("Erreur : veuillez entrer un nombre valide.")

Cet exercice vous aidera à comprendre comment utiliser les exceptions en Python pour gérer des erreurs qui pourraient survenir lors de l'exécution d'un programme. L'idée est de prévoir ces erreurs et d'agir en conséquence pour éviter que le programme ne plante.

Vous devez écrire une fonction qui permet de diviser deux nombres et de gérer les erreurs possibles lors de cette division. Le but est de comprendre comment utiliser le bloc try/except pour attraper des exceptions courantes, comme la division par zéro ou une entrée non valide.

Détails de la fonction :

  • La fonction s'appellera diviser.
  • Elle prendra deux arguments : numérateur et dénominateur.
  • La fonction doit gérer les erreurs suivantes :
    • Division par zéro : Si le dénominateur est égal à 0, la fonction doit afficher un message d'erreur approprié : "Erreur : Division par zéro non permise."
    • Entrée non numérique : Si l'un des deux arguments n'est pas un nombre (par exemple une chaîne de caractères), la fonction doit afficher un message d'erreur : "Erreur : Les deux valeurs doivent être des nombres."
  • Spécifier les exceptions : Capturer des exceptions spécifiques permet de mieux comprendre et gérer les erreurs. Éviter d'utiliser un except sans préciser le type d'exception.
  • Fournir des messages clairs : Les messages d'erreur doivent être compréhensibles pour l'utilisateur, indiquant ce qui s'est mal passé et, si possible, comment y remédier.
  • Ne pas masquer les erreurs : Éviter de capturer toutes les exceptions sans traitement approprié, car cela peut rendre le débogage difficile.
  • Utiliser le bloc finally pour le nettoyage : S'assurer que les ressources sont correctement libérées, même en cas d'erreur.
  • Lever des exceptions au lieu de retourner des codes d'erreur : Cela rend le code plus clair et facilite la gestion des erreurs.
  • Ne pas abuser des exceptions : Les exceptions doivent être utilisées pour des conditions exceptionnelles, pas pour le contrôle de flux normal du programme.

Exemple de mauvaise pratique :

try:
traiter_donnees()
except Exception:
pass # Mauvaise pratique, toutes les exceptions sont masquées

Exemple de bonne pratique :

try:
traiter_donnees()
except FileNotFoundError as e:
# Traitement approprié de l'exception
print(f"Erreur : {e}")

Depuis Python 3.11 : notes et groupes d'exceptions

Section intitulée « Depuis Python 3.11 : notes et groupes d'exceptions »

Python 3.11 a modernisé les exceptions sur trois points utiles à connaître.

Le plus simple et le plus quotidien : add_note() (PEP 678) attache un contexte à une exception existante sans la remplacer. La note apparaît ensuite dans la trace :

try:
age = int(valeur)
except ValueError as e:
e.add_note("Champ 'age' du fichier config.json")
raise

Pour les programmes concurrents (asyncio, traitements parallèles), les groupes d'exceptions (PEP 654) permettent de lever plusieurs erreurs à la fois et de les trier avec la syntaxe except* :

try:
raise ExceptionGroup("plusieurs échecs", [
ValueError("valeur invalide"),
KeyError("clé absente"),
])
except* ValueError as groupe:
print("valeurs :", [str(e) for e in groupe.exceptions])
except* KeyError as groupe:
print("clés :", [str(e) for e in groupe.exceptions])

Enfin, les messages d'erreur sont devenus plus précis : la trace souligne l'expression exacte qui a échoué (PEP 657), pas seulement le numéro de ligne, ce qui accélère le débogage sans rien changer à votre code.

Dans un script d'exemple, print() suffit pour afficher une erreur. Dans un vrai programme, il faut journaliser : le module logging enregistre l'erreur avec sa pile d'appels complète, horodatée, sans polluer la sortie standard. La fonction logging.exception() est faite pour ça, à appeler dans un bloc except :

import logging
try:
resultat = 10 / diviseur
except ZeroDivisionError:
logging.exception("Échec du calcul") # message + traceback complet

Quand vous relancez une erreur sous une forme plus parlante, chaînez avec raise ... from : la cause d'origine est conservée, ce qui rend le débogage bien plus simple.

def charger_config(chemin):
try:
with open(chemin, encoding="utf-8") as f:
return f.read()
except FileNotFoundError as e:
raise RuntimeError(f"Configuration illisible : {chemin}") from e

Enfin, quand une erreur précise est attendue et sans conséquence, contextlib.suppress exprime l'intention plus clairement qu'un try/except/pass :

from contextlib import suppress
with suppress(FileNotFoundError):
os.remove("cache.tmp") # si le fichier n'existe pas, on ignore
  • Le code risqué va dans try, la réaction dans except : le programme ne s'arrête plus brutalement.
  • Ciblez le type précis (ValueError, KeyError) plutôt qu'un except générique qui masque tout.
  • else s'exécute si aucune erreur, finally dans tous les cas (nettoyage, libération de ressources).
  • raise lève une erreur ; raise ... from conserve la cause d'origine pour le débogage.
  • Une exception personnalisée (classe héritant de Exception) clarifie les erreurs métier.
  • Les assertions servent au développement, pas à la gestion d'erreurs en production (désactivables avec -O).
  • En production, journalisez avec logging.exception() au lieu de print(), et n'avalez jamais une erreur en silence.
  • Depuis Python 3.11, add_note() enrichit une erreur de contexte et except* trie les groupes d'exceptions (utile en asyncio).

La gestion des erreurs se combine avec presque tout : lecture de fichiers, appels d'API et journalisation propre en font un réflexe quotidien.

Ce site vous est utile ?

Sachez que moins de 1% des lecteurs soutiennent ce site.

Je maintiens +700 guides gratuits, sans pub ni tracking. Un soutien, même symbolique, m'aide à couvrir l'hébergement et à garder ces ressources gratuites. Merci pour votre appui.

Le formulaire ne s'affiche pas ? Ouvrir Ko-fi dans un onglet.

Abonnez-vous et suivez mon actualité DevSecOps sur LinkedIn