UKOnline

Définir une exception

Terminons ce chapitre en voyant comment définir ses propres exceptions. Comme on a déjà pu le voir, une exception est un objet et du coup, il faut qu'il existe quelque part une classe pour qu'on puisse en créer des instances. Définir un nouveau type d'exception revient donc à définir une nouvelle classe.

Génération d'erreur

Avant de définir nos propres exceptions, voyons comment générer une erreur dans un programme grâce à l'instruction raise.

Il suffit en fait simplement d'utiliser le mot réservé raise suivi d'une référence vers un objet représentant une exception. L'exemple suivant reprend la fonction récursive permettant de calculer la factorielle d'un nombre naturel positif :

Par rapport à la version de la fonction présentée au chapitre 6, on a ajouté une instruction if testant si n est strictement négatif, dans lequel cas on génère une exception de type ArithmeticError, qui représente une erreur arithmétique.

Si on exécute ce programme qui tente de calculer la factorielle de $-12$, il va s'arrêter brutalement avec l'erreur suivante :

Traceback (most recent call last):
  File "program.py", line 8, in <module>
    print(fact(-12))
  File "program.py", line 3, in fact
    raise ArithmeticError()
ArithmeticError

L'erreur observée est de type ArithmeticError, et c'est précisément celle qu'on a générée à la deuxième ligne du corps de la fonction fact avec l'instruction raise. On va, évidemment, pouvoir se protéger de cette erreur à l'aide d'un try-except lorsqu'on appelle la fonction. Le programme suivant capture spécifiquement l'exception de type ArithmeticError :

Cette fois-ci, on n'aura plus de trace d'erreur puisque l'erreur est capturée et gérée. On obtient, par exemple, le résultat d'exécution suivant :

Entrez un nombre : -12
Veuillez entrer un nombre positif.

Si on avait entré du texte ne correspondant pas à un nombre entier, le gestionnaire d'erreur par défaut (le dernier except) aurait été exécuté.

Enfin, il faut savoir que, dans une fonction, l'instruction raise se comporte comme l'instruction return, à savoir qu'elle a pour conséquence que l'exécution du corps de la fonction est immédiatement quitté. L'erreur se propage ensuite en remontant la séquence des appels de fonctions, jusqu'à être attrapée. Si elle n'est jamais attrapée, le programme se termine en erreur.

Créer un type d'exception

Dans l'exemple précédent, on a généré une exception de type ArithmeticError, dont la classe existe déjà en Python. On pourrait se limiter aux types d'exceptions déjà existants, voire utiliser le type générique Exception, mais il est parfois plus pratique et plus lisible de définir ses propres types. Pour cela, il suffit de définir une nouvelle classe, comme le montre l'exemple suivant :

Cette classe est tout simplement vide puisque son corps n'est constitué que de l'instruction pass qui, pour rappel, ne fait juste rien.

On remarque néanmoins une petite différence par rapport à ce qu'on a vu sur la définition de classe, c'est le Exception qui a été ajouté entre parenthèses après le nom de la classe. Sans rentrer dans le détail, cela permet de signifier à Python que cette classe est de « type exception », ce qui permettra de l'utiliser avec try-except et raise.

Une fois cette classe définie, on va pouvoir l'utiliser. Définissons une fonction trinomialroots qui calcule et renvoie les racines d'un trinôme du second degré de la forme $ax^2 + bx + c$, mais qui, contrairement à la version vue au chapitre 3, génère une erreur lorsqu'il n'y a pas de racine réelle :

La quatrième ligne du corps de la fonction génère une erreur de type NoRootException à l'aide de l'instruction raise, dans le cas où le trinôme n'admet pas de racine réelle. Pour que ce programme fonctionne, il faut évidemment que la classe NoRootException soit définie dans le même fichier que la fonction trinomialroots.

Lorsqu'on appelle cette fonction, on va donc pouvoir utiliser l'instruction try-except pour attraper cette erreur, lorsqu'elle survient. Essayons, par exemple, de calculer et d'afficher les racines réelles du trinôme $x^2 + 2$. Pour cela, on appelle donc la fonction trinomialroots en lui passant en paramètres $1$, $0$ et $2$ puisque $x^2 + 2$ correspond à $a = 1$, $b = 0$ et $c = 2$ :

Puisque ce trinôme n'admet pas de racine réelle, la parabole correspondante se trouvant au-dessus de l'axe des $x$, une erreur sera générée et capturée par le bloc except, comme on le constate sur le résultat de l'exécution du programme :

Pas de racine réelle.

Tout ceci est donc possible car on a défini une nouvelle classe de « type exception », permettant de générer une erreur de ce type avec raise et de l'attraper avec except.

Exception paramétrée

Une exception se définit par une classe, et on peut donc y ajouter tout ce qu'on a vu, à savoir des variables d'instance, des accesseurs, des mutateurs et des méthodes. Cette possibilité permet de paramétrer l'exception, c'est-à-dire de lui ajouter de l'information. En récupérant l'objet correspondant à l'exception dans l'instruction except, on pourra l'utiliser et donc, par exemple, appeler ses méthodes.

Si on revient sur l'exemple des racines du trinôme du second degré, il pourrait être utile de connaitre la valeur du discriminant lorsqu'aucune racine réelle n'existe. On va pour cela ajouter une variable d'instance et un accesseur à la classe NoRootException :

On modifie ensuite l'instruction qui génère l'erreur dans la fonction trinomialroots, puisqu'il faut maintenant passer un paramètre lors de la création d'un nouvel objet de type NoRootException :

On peut maintenant récupérer la valeur du discriminant dans le bloc except, à partir de l'objet représentant l'exception qui s'est produite. Il suffit pour cela de faire appel à l'accesseur delta :

L'exécution de ce programme affiche donc la valeur dudit discriminant, et on voit bien qu'il est négatif :

Pas de racine réelle.
Delta = -8