Up.oN
#08/04/20225
Ce chapitre à été rédigé via OpenIA.

Contexte et notions de base

En Python, une exception représente une situation anormale qui interrompt le déroulement normal de l'exécution d'un programme. Contrairement aux erreurs de syntaxe (détectées lors de la lecture du code), les exceptions surviennent au moment de l'exécution. Elles permettent non seulement d'identifier les problèmes potentiels (division par zéro, accès à une clef inexistante dans un dictionnaire, etc.) mais surtout de les gérer de manière à éviter un arrêt brutal du programme.

La gestion des exceptions repose sur plusieurs constructions clés :

  • try: on place le code susceptible de provoquer une erreur à l'intérieur de ce bloc.
  • except: permet de capturer et de traiter spécifiquement un ou plusieurs types d'exceptions.
  • else: ce bloc optionnel s'exécute uniquement si aucun problème (aucune exception) n'est survenu dans le bloc try.
  • finally: ce bloc optionnel s'exécute dans tous les cas, qu'une exception soit levée ou non, afin de nettoyer ou libérer des ressources.

Bloc de base : try/except

La structure de base

La forme la plus simple consiste à entourer le code potentiellement problématique avec un bloc try, suivi d'un ou plusieurs blocs except pour gérer les erreurs:

try:
    # Code susceptible de générer une exception
    nombre = int(input("Entrez un nombre : "))
    resultat = 10 / nombre
    print("Résultat :", resultat)
except ZeroDivisionError:
    print("Erreur : division par zéro.")
except ValueError:
    print("Erreur : vous devez saisir un nombre valide.")

Dans cet exemple, si l'utilisateur saisit une valeur non numérique, la conversion en entier lèvera une exception ValueError et le bloc correspondant s'exécutera. De même, si l'utilisateur saisit zéro, la division par zéro lèvera un ZeroDivisionError.

Remarque: il est important d'être toujours le plus spécifique possible en ce qui concerne le type d'exception.

Capturer plusieurs exceptions dans un même bloc

Si le même traitement convient pour plusieurs types d'exceptions, on peut les regrouper sous la forme d'un tuple :

try:
    resultat = 10 / int(input("Entrez un nombre : "))
    print("Résultat :", resultat)
except (ZeroDivisionError, ValueError) as e:
    print("Erreur :", e)

Ici, peu importe si c'est une division par zéro ou une mauvaise conversion, l'exception sera capturée et affichée.

Le bloc : else

Le bloc else s'exécute uniquement si aucune exception n'est levée dans le bloc try. Son utilité est de séparer clairement le code qui risque de poser problème de celui qui correspond au cas de succès. Cela permet d'éviter qu'un code « normal » susceptible de générer une erreur par inadvertance ne soit inclus dans le bloc try.

Exemple:

try:
    nombre = int(input("Entrez un nombre : "))
except ValueError:
    print("Erreur : veuillez saisir un nombre valide.")
else:
# Ce bloc ne s'exécute que si tout s'est bien passé dans le try
    print(f"Vous avez saisi le nombre {nombre}.")

Ici, si l'utilisateur saisit bien une valeur numérique, c'est uniquement dans le bloc else que vous poursuivez le traitement. Cela permet de garder le code du traitement normal séparé de celui du traitement d'erreur.

Le bloc : finally

Le bloc finally est exécuté quand l'exception survienne ou non. Il est souvent utilisé pour libérer des ressources (fermer un fichier, libérer une connexion réseau, etc.) ou effectuer des actions de nettoyage indispensables, quelle que soit la réussite ou l'échec du bloc try.

Exemple:

try:
    fichier = open("donnees.txt", "r")
    contenu = fichier.read()
    print("Contenu :", contenu)
except FileNotFoundError:
    print("Erreur : fichier non trouvé.")
finally:
# Ce bloc sera exécuté quoiqu'il arrive : fermeture du fichier
    try:
        fichier.close()
    except NameError:
    # Si fichier n'a pas été ouvert
        pass
    print("Fermeture de la ressource terminée.")

Dans ce cas, même si le fichier n'existe pas et l'exception FileNotFoundError est levée, le bloc finally s'assure que toute opération (ici la tentative de fermeture) s'exécute, garantissant ainsi un nettoyage correct.
Source: note.nkmk.me - python-try-except-else-finally

Conseils pratiques et bonnes pratiques

Séparer les zones de risque et le traitement normal

Limitez le bloc try au strict minimum: ne placez dans le bloc try que les lignes susceptibles de lever l'exception que vous souhaitez gérer.

Utilisez else pour le code qui doit s'exécuter uniquement en cas de succès complet du bloc try.

Spécifiez toujours le type d'exception à capturer

Évitez l'usage de blocs except sans spécifier l'exception (ce qui attrape toutes les erreurs) afin de ne pas masquer des bogues inattendus.

# Mauvaise pratique
try:
# Code
except:
    pass  # masque toutes les erreurs

Si vous souhaitez récupérer toutes les erreurs "non fatales", utilisez plutôt :

except Exception as e:
    print("Erreur :", e)

Utilisez finally pour garantir le nettoyage

Toujours fermer les fichiers, libérer des connexions ou d'autres ressources dans un bloc finally, afin que ces actions soient effectuées même en cas d'exception.

Levez des exceptions personnalisées quand c'est nécessaire

Vous pouvez créer vos propres classes d'exception pour des cas spécifiques à votre application. Cela améliore la lisibilité et la gestion fine des erreurs.

class MonErreur(Exception):
    pass

def valider_age(age):
    if age < 0:
        raise MonErreur("L'âge ne peut pas être négatif.")
    else:
        return True

Documentez vos choix de gestion d'erreurs

Dans un code collaboratif, ou lorsque vous devez déboguer plus tard, commentez pourquoi un certain type d'exception est capturé et pourquoi telle ou telle branche est choisie.

Ne pas utiliser les exceptions pour le contrôle de flux normal

Bien que le paradigme EAFP (« Easier to ask for forgiveness than permission ») est courant en Python et autorise l'utilisation d'exceptions pour contrôler le flux (par exemple, accéder directement à une clef d'un dictionnaire et gérer l'exception KeyError plutôt que de tester son existence), évitez de vous reposer trop lourdement sur cette technique dans des situations où une vérification préalable (LBYL - « Look Before You Leap ») rendrait le code plus clair.

# Exemple EAFP :
try:
    valeur = mon_dict['clé']
except KeyError:
    valeur = valeur_par_defaut

# Alternative LBYL :
valeur = mon_dict.get('clé', valeur_par_defaut)

Testez vos gestionnaires d'erreur

Utilisez des tests unitaires pour vous assurer que votre code se comporte comme attendu face aux exceptions. Cela vous évite des surprises en production.

Récapitulatif avec un exemple complet

Voici un exemple qui intègre l'utilisation de try, except, else et finally :

class DivisionError(Exception):
"""Exception personnalisée pour la division."""
    pass
            
def diviser(a, b):
    try:
    # Bloc à risque : la division et la conversion
        a = float(a)
        b = float(b)
        resultat = a / b
    except ZeroDivisionError as e:
        print("Erreur : division par zéro.")
        # On peut lever ici une exception personnalisée si besoin :
        raise DivisionError("La division par zéro n'est pas permise.")
    except ValueError:
        print("Erreur : les valeurs doivent être numériques.")
        raise DivisionError("Conversion impossible en float.")
    else:
    # Ce bloc s'exécute si aucune exception n'est survenue
        print(f"Le résultat de la division est : {resultat}")
        return resultat
    finally:
    # Ce bloc s'exécute toujours, pour assurer le nettoyage ou un message de fin
        print("Fin de l'opération de division.")
            
# Exemples d'utilisation :
print("Exemple 1:")
try:
    diviser(10, 2)
except DivisionError as de:
    print(de)
            
print("\nExemple 2:")
try:
    diviser(10, 0)
except DivisionError as de:
    print(de)
            
print("\nExemple 3:")
try:
    diviser("dix", 2)
except DivisionError as de:
    print(de)            

Ce que produit cet exemple :

  • Exemple 1: Le bloc try réussit, le bloc else affiche le résultat, puis le bloc finally s'exécute.
  • Exemple 2 : La division par zéro déclenche un ZeroDivisionError capturé, le bloc except affiche le message et relance (optionnellement) une exception personnalisée, puis le finally est exécuté.
  • Exemple 3: Une valeur non numérique provoque une ValueError, le bloc except réagit, et encore une fois le finally est exécuté.

Conclusion

La gestion des erreurs en Python grâce aux blocs try, except, else et finally est une technique essentielle pour rendre vos programmes robustes et maintenables. Utiliser intelligemment ces blocs permet de :

  • Séparer le code potentiellement défaillant de celui de la logique normale (avec else).
  • Garantir un nettoyage systématique en cas d'erreur ou non (avec finally).
  • Capturer précisément les erreurs attendues sans masquer d'autres problèmes.