Python Decorators : bien les comprendre pour bien les utilizer

Les Python Decorators vous permettent d’améliorer votre influence sur les fonctionnalités de base d’une fonction sans pour autant modifier le code source sur lequel elle se fonde.

Décorateurs de fonction : de quoi s’agit-il et à quoi servent-ils ?

L’utilisation des Python Decorators n’est pas abordée en détail dans la plupart des tutoriels Python. Vous vous demandez pourquoi ? Pour comprendre les décorateurs de fonction, il est d’abord nécessaire de bien maîtriser les fonctions en elles-mêmes. Dans le code, les Python Decorators sont reconnaissables grâce à leur propre opérateur Python : il s’agit du symbole « @ », suivi du nom du décorateur de fonction.

Note

Si vous voulez en savoir plus sur les techniques de programmation avancée avec Python, consultez les articles suivants :

L’exemple de code suivant présente la syntaxe de base d’un appel du Python Decorator, sans qu’aucune fonctionnalité ne soit toutefois implémentée :

@decorator
def fonction():
 pass
Python

Dans cet exemple, le code enregistré sous « decorator » serait exécuté en cas d’appel de la fonction nommée « fonction ».

Souvent, les décorateurs de fonction sont également utilisés dans le cadre de la programmation orientée objet avec Python. Il existe par exemple un décorateur Python Property, utilisé comme un équivalent des méthodes « getter » et « setter » dans d’autres langages de programmation.

Conseil

Le langage de programmation Python est parfait pour les projets Web, entre autres grâce à ses solutions pratiques (comme les décorateurs de fonction). L’outil Deploy Now, proposé par IONOS, se prête aussi très bien à la réalisation de projets Web. L’outil Deploy Now vous permet de créer et de déployer facilement votre projet grâce à GitHub, de façon à bénéficier d’une vue d’ensemble sur votre projet à tout moment.

Python Decorators : utilisation

Améliorer les fonctionnalités de base avec les Python Decorators

Dans la plupart des cas, les Python Decorators sont utilisés pour améliorer les fonctionnalités de base d’une fonction. Cette solution peut s’avérer pratique si vous avez recours à la même fonction de base dans le cadre de différents cas d’utilisation et que vous souhaitez l’améliorer dans certaines situations seulement. Vous trouverez ci-dessous un exemple de code simple, mais prêt à être exécuté. Découvrez comment utiliser un Python Decorator pour améliorer une fonctionnalité :

def dec(fonction):
 def foo(x):
  print("Avant l'appel de la fonction de" + fonction.__nom__)
  fonction(x)
          print("Avant l'appel de la fonction de" + fonction.__nom__)
 return foo
@dec
def bar(y):
 print("Appel de la fonction bar avec la valeur" + str(y))
bar("Test")
Python

Dans cet exemple de code, nous commençons par créer le décorateur nommé « dec » ; celui-ci contient une fonction portant le nom « foo ». Comme vous pouvez le voir, le décorateur de fonction n’est finalement rien de plus qu’une fonction « wrapper » autonome. Dans notre cas, elle contient une autre fonction nommée « foo ». Dans cette fonction « foo », il faut commencer par indiquer que nous nous trouvons avant l’appel de la fonction transmise au décorateur dans le paramètre « fonction ». La fonction du paramètre est ensuite exécutée. Un nouvel appel de la fonction « print » de Python est alors effectué ; il indique que nous nous trouvons après l’appel de la fonction enregistrée en tant que paramètre.

La deuxième partie du code comprend une définition de la fonction appelée « bar », qui reçoit un paramètre de transfert nommé « y ». Il est facile de comprendre la fonctionnalité de « bar » : elle affiche à l’écran la phrase « Appel de la fonction bar avec la valeur y », en utilisant pour « y » la valeur transmise en tant que paramètre. La fonction « bar » a pour particularité d’être « décorée ». Vous pouvez le voir dans l’exemple de code, à la ligne « @dec », avant la définition de la fonction.

Mais que se passe-t-il exactement lorsqu’une fonction a été « décorée » ? Imaginons que nous n’ayons pas spécifié le Python Decorator, à savoir la ligne de code « @dec ». L’appel de la fonction « bar », qui clôt notre exemple de code, donnerait alors le résultat suivant :

Appel de la fonction bar avec la valeur Test
Python

Ici, il se passe exactement ce qui est prévu pour un appel de fonction : la chaîne Python « Test », transmise pour le paramètre « y », est insérée dans la déclaration « print ». Le résultat de la fonction reflète donc cette action.

Recréons maintenant le résultat du même appel de la fonction « bar », cette dernière étant cette fois « décorée » à l’aide de notre Python Decorator :

Avant l'appel de la fonction bar
Appel de la fonction bar avec la valeur Test
Après l'appel de la fonction bar

Ce que vous voyez alors peut vous surprendre : une fois « décorée », notre fonction n’affiche plus seulement à l’écran le résultat de sa propre déclaration « print ». En réalité, le résultat de notre décorateur de fonction a été « encapsulé », de manière que les deux déclarations « print » de la fonction auxiliaire « foo » soient elles aussi prises en considération. La principale fonctionnalité de la fonction « bar » a donc été améliorée par l’introduction de deux résultats « print » supplémentaires, et ce grâce à l’utilisation du Python Decorator.

Cet exemple reste bien sûr artificiel et ne s’inscrit dans aucune logique de programmation approfondie. Il devrait toutefois vous aider à mieux comprendre le fonctionnement des Python Decorators. Il va de soi que vous pouvez intégrer la fonctionnalité Python de votre choix à la fonction « Decorator ».

Python Decorators : extraire des conditions récurrentes

Vous souhaitez peut-être conditionner l’exécution de certaines de vos fonctions. Pour ce faire, les déclarations « if…else » en Python vous sont probablement déjà familières. Si ces conditions doivent être vérifiées à différents emplacements, il peut toutefois s’avérer judicieux, pour garantir la lisibilité de votre code, d’externaliser cette condition en faisant appel à un Python Decorator.

L’exemple de code ci-dessous devrait vous permettre de visualiser la manière dont vous pouvez utiliser votre Python Decorator. Quelques opérateurs mathématiques ne sont définis que sur les nombres naturels ; un décorateur de fonction chargé de vérifier si le paramètre de transfert d’une fonction correspond ou non à un nombre naturel pourrait donc vous être utile.

def nombre_naturel(fonction):
 def test(x):
  if type(x) == int and x > 0:
   return fonction(x)
  else:
   raise Exception("L'argument n'est pas un nombre naturel")
@nombre_naturel
def fac(n):
 if n == 1:
  return 1
 else:
  return n * fac(n-1)
print(fac(5))
print(fac(-1))
Python

Dans le code ci-dessus, nous commençons par définir notre Python Decorator appelé « nombre_naturel ». Il permet de vérifier si l’argument de la fonction qui lui a été transmise (« fonction ») correspond ou non à un nombre naturel. Pour ce faire, la condition « if » vérifie tout d’abord le type de l’argument. Un test est également réalisé pour déterminer si l’argument est un nombre positif supérieur à 0. Dans l’affirmative, la fonction transmise au décorateur en tant que paramètre est exécutée. Dans le cas contraire, une exception précisant que l’argument de la fonction ne correspond pas à un nombre naturel est renvoyée.

En pratique, le fonctionnement de notre Python Decorator peut être illustré par l’observation de la fonction qu’il « décore », appelée « fac ». Celle-ci est définie dans le code et appelée une première fois avec la valeur « 5 », puis une seconde avec la valeur « -1 ». Le résultat se présente alors comme suit :

120
Traceback (most recent call last):
    File "<pyshell#17>", line 1, in <module>
        fac(-1)
    File "<pyshell#11>", line 6, in test
        raise Exception("L'argument n'est pas un nombre naturel")
Exception: L'argument n'est pas un nombre naturel

Vous voyez tout d’abord le nombre « 120 », qui correspond à la factorielle de 5. La fonction factorielle fonctionne donc pour les nombres naturels. L’appel de cette fonction factorielle avec un nombre négatif entraîne toutefois une erreur… due au Python Decorator ! Un nombre négatif n’étant pas un nombre naturel, la fonction factorielle ne doit pas être exécutée ici.