Gestion des exception: le cas du Python

By jefcolbi on November 07, 2025
Reading time: 7 minutes

Gestion des exception: le cas du Python

Bonjour le monde!

La gestion des erreurs dans tout programme informatique est necessaire. Il est important pour tout développeur d'en avoir une parfaite compréhension et maitrise.

Table des matières

Introduction

Un programme informatique ne peut se passer d'erreurs. Il existe differents types d'erreur. Des erreurs qui provoquent les crashs de programmes celles qui provoquent des faux resultats sans interrompre le programme et meme des erreurs qui empechent un programme de se terminer sans toutefois continuer son execution normale. Les exceptions sont une technique de gestion des erreurs dans un programme informatique. Beaucoup de programmeurs utilisent les exceptions sans veritablement en saisir la profondeur. Je vais tenter de vous expliquer dans cet article les tenants et les aboutissements des exceptions de façon claire, simple, limpide et digeste. D'abord un peu d'histoire!

Histoire

Revenons un peu en arrière. On a crée les ordinateurs, la programmation et Boom Denis Ritchie, gloire à lui, crée le C. Et la programmation décolle. Les programmes deviennent volumineux, les erreurs se multiplient, les codes sources sont partagés entre plusieurs developpeurs. Chacun a sa façon de gérer les erreurs. C'est la pagaille générale. Et puis, une allure générale se dessine et une sorte de consensus nait. Qu'est donc t-il? Nous sommes dans le contexte suivant: notre langage est le C et le paradigme est la programmation procedurale. Alors, le premier consensus est le suivant: les fonctions qui peuvent génerer une quelconque erreur doivent retourner un entier. Si la fonction a rencontré une erreur on retourne la valeur -1. D'où vous verrez regulierement la forme suivante de code en C:

if(ma_fonction() == -1)
{
    printf("Une erreur s'est produite\n");
    exit(-1);
}

Tout simplement on appelle la fonction et on teste sa valeur de retour, si elle correspond à -1, on signale qu'on a rencontré une erreur et on sort. Une implementation de la fonction ma_fonction() est generalement comme suit:

int ma_fonction()
{
    ...
    ...
    if(blabla == xxx)
        return -1;

    ...
}

Mais maintenant comment déterminer l'erreur qui s'est produite. Là encore on a d'abord commencé à renvoyer differentes valeurs de retour, genre -1 pour l'erreur X, -2 pour l'erreur Y etc. Comme vous pouvez l'imaginer, ça devient penible au niveau de l'appel de la fonction parce qu'on doit comparer la valeur avec plusieurs if ou un switch pour déterminer l'erreur. Ensuite on s'est dit, on va utiliser une valeur pour signaler qu'il y'a une erreur, et on va passer un pointeur vers une structure erreur pour avoir l'erreur à la fin de l'appel de la fonction. Ça a donné ceci

struct Error
{
    int code;
    char *chaine;
};

int ma_fonction(struct Error &error)
{
    ...
    if(blabla == xxx)
    {
        error->code = 100;
        error->chaine = chaine_allouee_precedemment;
        return -1;
    }
}

// et on appelait comme ceci
stuct Error error;

if(ma_fonction(&error) == -1)
{
    print("Error %d: %s", error.code, error.chaine);
    exit(-1);
}

Eh bien on voit bien que c'est bien mieux. Mais figurez vous que ce n'est pas suffisant. En effet passez un pointeur tout le temps est fastidieux. Et peut causer des erreurs. Quelqu'un s'est dit, "hummm puisque on teste le resultat d'une fonction juste après la sortie d'une fonction et qu'un programme ne peut appeler qu'une fonction à la fois, pourquoi ne pas donc avoir une seule et unique structure qui sera globale qui contiendra la dernière erreur rencontrée." et Bam chose dite chose faite. On a inventé lastError. Alors lastError fonctionne comment? Il s'agit tout simplement de definir une structure et des fonctions pour manipuler la dite structure. On ne manipule pas directement la structure. On propose 3 fonctions: clearLastError, setLastError et getLastError. Une fonction qui veut utiliser la dernière erreur (lastError) doit d'abord la nettoyer au debut de son execution en appelant la fonction clearLastError, et lorsqu'elle rencontre une erreur elle appelle setLastError, passant en general un code numerique et une chaine de caractère qui décrit l'erreur. La fonction appelante teste la valeur de retour de la fonction appelée, si c'est -1, elle appele getLastError pour récuperer l'erreur. Exemple:

int ma_fonction()
{
    clearLastError();
    ...
    if(blabla == xxx)
    {
        setLastError(100, "bete erreur");
        return -1;
    }
}

// pour appeler on fait
if(ma_fonction() == -1)
{
    int code=0;
    char *chaine;
    getLastError(&code, &chaine);
    print("Error %d: %s", code, chaine);
    exit(-1);
}

N'est ce pas, fin, magique et beau? Alors pour infos, ce style est toujours utilisé en GNU C.

Voilà jusque là tout allait deja bien. Et puis les langages de haut niveau sont apparus et un gars s'est dit: "hummm, au lieu d'avoir à manipuler une variable globale ce qui n'est pas si mal, pourquoi ne pas avoir un mécanisme qui permet d'interrompre immédiatement l'execution de la fonction tout en precisant la raison pour laquelle on a interrompu ladite fonction." et Booom! Ave Maria on a crée les exceptions! Voilà donc ce que sont les exceptions. Un moyen pour interrompre l'execution d'une fonction tout en donnant un moyen de connaitre pour qu'elle raison on a interrompu la fonction. Si vous comprenez ceci vous avez déjà tout compris et je n'ai plus rien à vous expliquer. Aurevoir 👋🏽🚶🏽‍♂️! Si vous continuez alors surement vous voulez mieux comprendre. Je vais étancher votre soif 🍹. Alors, on a decidé que la fonction appelée (callee en anglais) devra "lever l'exception" et la fonction appelante (caller) devra "capturer l'exception". Terminé. J'ai tout dit. Si vous écrivez une fonction c'est à vous de lever une exception en cas d'erreur, si vous appelez une fonction c'est à vous de capturer l'exception. Concretement, dans l'implementation courante, les exceptions sont justes des classes. On les lève avec les mots clés raise ou throws et on les capture avec catch ou except. Bon voyons un peu le cas des exception en Python.

Les exceptions en Python

Comme je l'ai dit plus haut les exceptions sont des classes. Plus précisement, une exception est toute sous-classe ou classe fille de la classe Exception. Autrement dit la classe Exception est la classe mère de toutes les exceptions. Où encore toutes les exceptions sont des classes qui dérivent de la classe spéciale Exception. Et oui, c'est une classe spéciale. Parce que l'interpreteur Python connait et reserve un traitement particulier à cette classe et à ses classes filles. Maintenant si vous faites raise sur une classe qui n'herite pas d'Exception je ne sais pas ce qui va se passer. Je n'ai jamais essayé. C'est juste bête. N'essayez même pas de le faire. C'est juste bete. Pour mieux comprendre on va prendre un exemple. On va travailler avec la fonction divise_positif(n, m) qui est une fonction qui retourne la division d'un n nombre positif par un autre nombre m positif.

####Methode naive

def moitie_positif(n, m):
    if(n < 1 or m < 1):
        return None
    else:
        return n/2

Simplement la condition pour bien executer cette fonction est que n doit etre positif et m doit etre positif et different de 0. Pour appeler cette fonction, je fais comme ceci

n = input("entrer un entier positif")
m = input("entrer un entier positif")
res = divise_positif(n, m)
if res == None:
    print("Verifiez votre n ou m")
else:
    print("Le resultat est", n)

Biensur ce code est correct. Mais quelle est le problème ici ? Si on veut informer la fonction appelante de l'erreur, on doit utiliser les methodes décrites dans la partie historique plus haut ou.... utiliser les exceptions. Comme ceci

def divise_positif(n, m):
    if m == 0:
        raise ZeroDivisionError("m est null")
    elif n < 0:
        raise ValueError("n est inferieur à 0")
    elif m < 0:
        raise ValueError("m est inferieur à 0")
    else:
        return n/2

Et maintenant dans la fonction appelante, on gère comme ceci

try:
    n = input("entrer un entier positif")
    m = input("entrer un entier positif")
    res = divise_positif(n, m)
    print("Le resultat est ", res)
except ZeroDivisionError:
    print("Es tu fou? pourquoi tu veux faire une division par zero?")
except ValueError as e:
    print(str(e))
except:
    print("Je n'ai pas prevu l'exception si: {}".format(str(e)))

Très bien. Simplement dans le block try: je mets le code "qui ne considere aucun problème", dans le block except ZeroDivisionError je mets le code dans le cas où une erreur de type ZeroDivisionError est lévée, ensuite dans le block ValueError je capture l'instance de l'exception lévée avec as e et j'affiche la description de l'erreur telle que lévée par la fonction appelée, et enfin dans le block except: je mets un print qui doit s'executer dans le code où une exception differente de ValueError et DivisionByZeroError est lévée. Cet exemple qui est parfait montre que:

  1. on peu capturer plusieurs types d'exception et pour chaque type on peut effectuer un traitement spécifique
  2. on peut capturer l'instance de l'exception lévée (n'oubliez pas que c'est une classe qu'on instancie)
  3. on doit avoir un comportement pour tous autres types d'exception non gérées.
  4. on doit definir le comportement normal sans exception lévée.

Si vous avez retenu ces 4 propositions vous avez déjà compris comment utiliser les exceptions et comment bien le faire. Mais bon voyons d'autres exemples.

Utilisations

Mieux vaut demander pardon que de dire s'il te plait

Hummm, je vois déjà certains me dirent que j'incite au péché. Non! Je ne vous incite pas au péché. Je vous présente juste une technique de programmation. Vous allez mieux comprendre par un exemple. Supposons que vous avez deux fonction racine_negatif() et racine_postif() qui permettent, respectivement, de calculer la racine carré d'un nombre positif ou celle d'un nombre négatif. Voici ce que vous allez faire:

if n < 0:
    res = racine_negatif(n)
else:
    res = racine_positf(n)
print("le resultat est", res)

En utilisant les exceptions ça devient

try:
    res = racine_positif(n)
exception:
    res = racine_negatif(n)
print("le resultat est", res)

Dans la première version, je cherche à savoir SI (la permission) je peux appeler telle ou telle fonction, mais dans la deuxième j'appelle d'abord une fonction et si il y'a une exception (je demande pardon et) j'appelle l'autre fonction. Simplement avec les exceptions, on peut ignorer le test sur la qualité des paramètres d'une fonction. Si y'a aucune erreur on continue sans problème sinon on corrige en appelant une autre fonction. Voici un autre exemple très frequent en Django. Dans les modeles on a:

class Categorie(Model):
    nom = CharField(max_length=100)

Dans la vue on a:

try:
    cat = Categorie.objects.get(nom='argent')
except:
    cat = Categorie.objects.create(nom='argent')

Qui signifie simplement que pour avoir la categorie argent, je fais comme si elle existait deja dans ma base de données si par malheur elle n'existe pas, je demande pardon en la créant et en continuant. Techniques de pro 😎!

Gestion des ressources

Les exceptions permettent de mieux gérer les ressources parce qu'on connait exactement l'erreur qui s'est produite et on peut entreprendre les meilleures actions. Exemple:

try:
    s = socket.connect(host, port)
    s.send('bonjour')
    data = s.recv(10)
    traitement(data)
    s.close()
except SocketTimeout:
    print('Impossible de se connecter, verifier l'hote)
    # pas besoin de fermer la socket
except SocketClosed:
    print('Socket fermee pendant l'envoi ou la reception de donnees)
    # la socket a été fermée
except Exception as e:
    print("Erreur non liée a la socket: ", str(e))
    # l'erreur ne concerne pas la socket on doit a tout prix la fermer
    s.close()

L'exemple est clair. Tout est clair maintenant dans votre esprit.

Conclusion

On a fait le tour du concept Exception en general et en Python. On a vu l'évolution de la gestion des erreurs mais aussi comment les pros utilisent les exceptions pour faire des programmes stables qui ne crashent pas à tout bout de champ. On a pu se placer du point de vue de celui qui doit lever l'exception (celui qui redige une fonction) et celui qui doit capturer l'exception (celui qui appelle la fonction). Maintenant on devrait être capable de faire de meilleurs programmes resistants aux erreurs prévues et non prévues. J'espère que cet article que vous avez plu, si c'est le cas, likez, partagez et si vous avez une question ou une remarque, commentez.

Comments (0)

-- Signin to comment --

No comments yet. Be the first to comment!