Gérer des exceptions avec des décorateurs en python
- 2021-02-22
- Publié par : Christophe DELEUZE
- Catégorie : Python
Parfois, gérer des exceptions peut s’avérer être répétitif. Aujourd’hui, pour le premier article du blog, nous allons voir comment le faire de façon élégante.
Les décorateurs
Les fonctions en Python sont des objets de première classe, ce qui signifie qu’elles peuvent être affectées à une variable, passées en argument, renvoyées par une autre fonction et stockées dans n’importe quelle structure de données.
def fonction_exemple():
print("Exemple de fonction appelée")
une_variable = fonction_exemple
une_variable()
Comme vous pouvez le voir, nous avons assigné fonction_exemple à une_variable, ce qui rend une_variable appelable. La sortie pour ce qui suit sera :
Exemple de fonction appelée
La propriété d’objet de première classe d’une fonction nous aide à utiliser le concept de décorateurs en Python. Les décorateurs sont des fonctions qui prennent comme argument une autre fonction. Cela permet d’exécuter une logique avant et après l’exécution de la fonction argument.
def exemple_decorateur(func):
print("Décorateur appelé")
def fonction_interne(* args, ** kwargs):
print("Appel de la fonction")
func(* args, ** kwargs)
print("L'exécution de la fonction est terminée")
return fonction_interne
@exemple_decorateur
def une_fonction():
print("Exécution de la fonction")
# La logique de la fonction va ici
une_fonction()
Comme vous pouvez le voir, nous utilisons le décorateur en utilisant @exemple_decorateur au-dessus de la fonction qui doit être passée en argument à la fonction décoratrice. Dans ce cas, une_fonction sera passée comme argument à exemple_decorateur. La sortie de l’extrait de code ci-dessus sera :
Décorateur appelé
Appel de la fonction
Exécution de la fonction
L'exécution de la fonction est terminée
Cette magie est possible, car @exemple_decorateur est le raccourci de exemple_decorateur(une_fonction)(). Vous noterez qu’au niveau du décorateur, la fonction exemple_decorateur récupère la référence de une_fonction tandis que les arguments sont récupérés fonction_interne.
En complément, si votre fonction doit retourner une valeur, il vous suffira d’ajouter un return positionné dans la fonction décoratrice.
L’exemple suivant illustre ce cas avec un pré-traitement et un post-traitement :
def exemple_decorateur(func):
print("Décorateur appelé")
def fonction_interne(* args, ** kwargs):
print("Avant l'appel de la fonction je fais un pré-traitement.")
resultat_final = "Le résultat suivant a été préparé par un pré-traitement :"
print("Appel de la fonction")
resultat = func(* args, ** kwargs)
resultat_final += resultat
print("Résultat de la fonction appelée dans le décorateur:", resultat)
print("L'exécution de la fonction est terminée, j'ajoute un pré-traitement.")
resultat_final += ", puis il a subit un post-traitement"
return resultat_final
return fonction_interne
@exemple_decorateur
def une_fonction():
return "Résultat de la fonction"
une_fonction()
Le résultat attendu sera :
Décorateur appelé
Avant l'appel de la fonction je fais un pré-traitement.
Appel de la fonction
Résultat de la fonction appelée dans le décorateur: Résultat de la fonction
L'exécution de la fonction est terminée, j'ajoute un pré-traitement.
Le résultat suivant a été préparé par un pré-traitement :Résultat de la fonction, puis il a subit un post-traitement
Les exceptions avec try et except
Prenons un exemple de fonctions qui nécessitent le traitement d’exceptions similaires. Nous prendrons comme exemple simple le calcul de surfaces. Nous allons pour l’instant afficher les erreurs si un type non pris en charge est passé comme argument à la fonction de calcul. Pour rappel, python préconise de gérer tout le temps les erreurs de la façon suivante :
Les erreurs ne doivent jamais passer en silence.
À moins d’être explicitement réduit au silence.
De manière classique, la façon normale de détecter des erreurs de type pour des fonctions de calcul serait d’utiliser un bloc try / except TypeError qui permettrait de détecter une erreur de type (TypeError) si le type de l’argument ne correspond pas à ce qui est attendu. Cela devrait ressembler à ça :
def surface_carre(longueur):
try:
print(longueur ** 2)
except TypeError:
print("surface_carre ne prend uniquement que des nombres comme argument")
def surface_cercle(rayon):
try:
print(3.142 * rayon**2)
except TypeError:
print("surface_cercle ne prend uniquement que des nombres comme argument")
def surface_rectangle(longueur, largeur):
try:
print(longueur * largeur)
except TypeError:
print("surface_rectangle ne prend uniquement que des nombres comme argument")
surface_carre(2)
surface_cercle(2)
surface_rectangle(2, 4)
surface_carre("une chaine")
surface_cercle("une autre chaine")
surface_rectangle("encore une autre chaine", "et une autre chaine")
Générer des exceptions avec un décorateur
Vous remarquerez que l’exemple précédent semble répétitif et c’est exactement ce que nous devrions éviter dans la mesure du possible. Nous pouvons donc utiliser la magie des décorateurs ici et observer que le code parait beaucoup plus propre de cette façon.
def exception_handler(func):
def fonction_interne(* args, ** kwargs):
try:
func(* args, ** kwargs)
except TypeError:
print(f"{func.__name__} ne prend uniquement que des nombres comme argument")
return fonction_interne
@exception_handler
def surface_carre(longueur):
print(longueur * longueur)
@exception_handler
def surface_cercle(rayon):
print(3.14 * rayon**2)
@exception_handler
def surface_rectangle(longueur, largeur):
print(longueur * largeur)
surface_carre(2)
surface_cercle(2)
surface_rectangle(2, 4)
surface_carre("une chaine")
surface_cercle("une autre chaine")
surface_rectangle("encore une autre chaine", "et une autre chaine")
La sortie de ce qui suit sera :
4
12.568
8
surface_carre ne prend uniquement que des nombres comme argument
surface_cercle ne prend uniquement que des nombres comme argument
surface_rectangle ne prend que uniquement des nombres comme argument
Enfin, nous pouvons étendre la capacité de générer des erreurs dans la fonction exception_handler en ayant des exceptions personnalisées et en étendant davantage ses utilisations. C’est l’un des exemples qui nous permet de gérer proprement les exceptions.
Le mot de la fin
Rien de sorcier dans toute cette histoire. Toutefois, si vous avez des questions, les commentaires sont là pour ça.
Excellent le dernier exemple!
J’ai lu in certai nombre d’exemple de décorateur et je n’avais jamais trop compris l’utilité.
Merci!!
Moi aussi j’ai rarement vu une telle simplicité dans la conception et l’explication des décorateurs. Je me disais que les decorateurs c’est comme qui dirait consomme et passe tu ne comprendras pas ce truc là, sauf que j’ai percé ses mysteres grace à ce blog.
Merci
Bonjour,
Merci pour cet excellent article très pédagogique. 🙏
Petite question : dans le dernier exemple de la section “Les décorateurs”, ne faudraitil pas rajouter “Décorateur appelé” en première ligne sur la sortie ? 🤔
Bien vu merci :). C’est corrigé.