Les décorateurs Python sont des modèles qui ajoutent de nouvelles fonctionnalités à un objet déjà existant sans avoir besoin de modifier sa structure. Le décorateur est appelé avant la fonction à modifier.
On l’utilise pour modifier la fonctionnalité d’une méthode, d’une fonction ou d’une classe dynamiquement sans avoir besoin de modifier l’objet lui-même.
Pour comprendre les décorateurs, commencez par comprendre comment Python permet de définir des fonctions imbriquées, c’est-à-dire des fonctions à l’intérieur d’autres fonctions.
Les fonctions imbriquées
def externe(n):
print("J'englobe la fonction interne")
def interne():
print("J'hérite de la fonction englobante le", n)
interne()
externe(5)
L’exécution du code.
J'englobe la fonction interne
J'hérite de la fonction englobante le 5
Comme vous pouvez le constater, on a appelé la fonction interne
à l’intérieur de la fonction externe
. En fait, lorsque nous avons appelé la fonction externe
, la fonction interne a également été exécutée. De plus, la fonction interne
a accédé aux paramètres de la fonction externe
.
Affecter une fonction à un nom
En Python, les fonctions sont également des objets comme les autres. Par conséquent, elles peuvent être affectées à des noms, puis renvoyées par d’autres fonctions.
La différence entre les objets de données et les fonctions est que les fonctions peuvent être appelées.
Par exemple, nous pouvons assigner la fonction nommée externe
à un nom fonc
puis l’appeler avec ce nom :
fonc = interne # Le nom 'fonc' est un alias de 'interne'.
fonc() # Nous appelons 'interne' 'avec son alias.
Maintenant, les choses vont devenir plus intéressantes. Nous allons toujours définir la fonction externe
avec la fonction interne
. Mais cette fois, avec une nouvelle fonctionnalité. Au lieu d’appeler la fonction interne
, nous allons renvoyer sa valeur dans la fonction externe
avec un return
, puis utiliser la technique d’affectation précédente.
def externe(n):
def interne():
print("J'hérite de la fonction englobante le", n)
return interne
externe(5) # rien ne sera exécuté
fonc = externe(10)
fonc()
La surprise ici est que l’exécution de externe
, ne semble rien faire.
Mais en fait, externe
a fait quelque chose d’important. Elle a créé une nouvelle fonction en lui passant un argument, puis a renvoyé la valeur de cette nouvelle fonction.
La nouvelle fonction a été affectée au nom fonc
. Ce nom peut donc être utilisé pour appeler la fonction nouvellement définie.
Une fonction comme argument
Maintenant, il reste une chose à ajouter, et nous seront prêts à créer des décorateurs Python. Une fonction peut à la fois retourner une fonction et prendre une fonction comme argument. Vous pouvez donc écrire ceci :
def externe(fonc):
def interne():
print("Je suis avant la fonction")
fonc()
print("Je suis après la fonction")
return interne # "externe" renvoie une nouvelle fonction!
L’objectif de la fonction externe
est maintenant de prendre une fonction fonc
comme argument, elle sera utilisée pour construire la nouvelle fonction qui sera renvoyée. Par exemple, définissons une fonction bonjour
.
def bonjour():
print('Bonjour')
Maintenant nous allons passer la fonction bonjour()
à externe()
, qui retournera une nouvelle fonction qui fait tout ce que bonjour() a fait, mais elle en fait plus. Le résultat de la nouvelle fonction est affecté au nom new
.
new = externe(bonjour)
Maintenant, new
est un alias pour la fonction nouvellement créée. Vous pouvez utiliser new
pour appeler cette fonction aussi souvent que vous le souhaitez.
new()
L’exécution du code.
Je suis avant la fonction
Bonjour
Je suis après la fonction
Voici un autre exemple. Nous pouvons utiliser externe()
pour englober n’importe quelle autre fonction, puis affecter le résultat au nom new
qui est le nom de la nouvelle fonction, et nous pouvons ainsi l’appeler.
def print_nums():
for i in [1, 2, 3]:
print(i, end=' ')
print()
new = externe(print_nums)
new()
L’exécution du code.
Je suis avant la fonction
1 2 3
Je suis après la fonction
Nous avons créer une nouvelle fonction autour de print_nums
, puis nous l’avons affecté à new
.
C’est le concept d’une fonction encapsulée.
Toute fonction que nous encapsulons fera ce qu’elle faisait auparavant, mais elle affichera également un message avant et après l’exécution. Nous pouvons même réattribuer l’ancien nom de la fonction ! Voici un exemple :
print_nums = externe(print_nums)
print_nums()
D’accord, c’est bizarre. Le nom de la fonction, print_num
, est-il réaffecté à une nouvelle version de … lui-même ? Oui, c’est exactement ce qui se passe.
Vous aurez le même résultat.
Si nous résumons ce concept en un code, nous aurons.
def externe(fonc):
# créer une nouvelle fonction
def interne():
print(message 1)
fonc()
print(message 2)
return interne
# la nouvelle fonction remplace l'ancienne
fonc = externe(fonc)
Le point clé ici est que le nom fonc
, qui au début faisait référence à une fonction simple, se réfère maintenant à une nouvelle version, qui est améliorée.
Créer un décorateur Python
Un décorateur Python réaffecte un nom de fonction à une version encapsulée de la fonction d’origine. En fait, ces concepts sont utiles pour le débogage et les tests de performance. Reprenons l’exemple précédent.
def mon_decorateur(ma_fonction):
# Prendre une fonction normaL.
# Qui servira à créer une nouvelle version.
# Renvoyez cette nouvelle fonction
def nouvelle_fonction():
print('Je fais des choses supplémentaires.')
ma_fonction()
print("Je fais d'autres choses supplémentaires.")
return nouvelle_fonction
Donc, le décorateur crée une nouvelle fonction…et la renvoie. Nous l’attribuons au nom de la fonction d’origine. Nous pouvons faire la même chose pour toutes les fonctions que nous choisissons !
fonc1 = mon_decorateur(fonc1) # encapsuler fonc1
fonc2 = mon_decorateur(fonc2) # encapsuler fonc2
fonc3 = mon_decorateur(fonc3) # encapsuler fonc3
La dernière étape consiste à appliquer la syntaxe de décoration. Cette syntaxe génère une ligne similaire à chacune des trois lignes ci-dessus.
@mon_decorateur
def bonjour():
print('Bonjour!')
En fait, Python traduit cela en :
def bonjour():
print('Bonjour!')
bonjour = mon_decorateur(bonjour)
Voici la syntaxe qui résume le fonctionnement des décorateurs Python :
@mon_decorateur
def fonc(args):
instructions
Ce qui équivaut le code suivant :
def fonc(args):
instructions
fonc = mon_decorateur(fonc)
Les décorateurs avec des arguments
Comment faire si nous voulons décorer une fonction avec des arguments ?
Et si elle nous renvoie une valeur ?
Le premier changement consiste à passer la liste d’arguments à la fonction encapsulée en utilisant * args et ** kwargs.
def mon_decorateur(ma_fonction):
def nouvelle_fonction(*args, **kwargs):
print('Je fais des choses supplémentaires.')
ma_fonction(*args, **kwargs)
print("Je fais d'autres choses supplémentaires.")
return nouvelle_fonction
L’autre changement consiste à faire en sorte que nouvelle_fonction()
renvoie la valeur de ma_fonction()
.
def mon_decorateur(ma_fonction):
def nouvelle_fonction(*args, **kwargs):
print('Je fais des choses supplémentaires.')
valeur = ma_fonction(*args, **kwargs)
print("Je fais d'autres choses supplémentaires.")
return valeur
return nouvelle_fonction
Maintenant, la valeur renvoyée par la fonction ma_fonction()
, est également renvoyée par nouvelle_fonction()
.
Si ma_fonction()
n’a pas de valeur à renvoyer, None
sera renvoyée par défaut.
Le code qui correspond à un décorateur avec des arguments est :
def mon_decorateur(ma_fonction):
def nouvelle_fonction(*args):
# Créer une nouvelle fonction
print(message 1)
valeur = ma_fonction(*args)
print(message 2)
return valeur
return nouvelle_fonction
@mon_decorateur
def fonc(args...)
instructions
Prenons un exemple simple pour que vous puissiez assimiler.
Nous avons une fonction somme
qui nous retourne la somme de deux entiers.
def somme(x,y):
return x+y
Pour une raison ou une autre, nous voulons multiplier la valeur retournée par 2, sans avoir besoin de modifier la fonction somme
.
Nous allons donc appliquer ce que nous avons appris avec les décorateurs Python.
def deco(fonc):
def nouvelle(x,y):
val = fonc(x,y)* 2
return val
return nouvelle
somme = deco(somme)
print(somme(2,3))
Si nous appliquons la syntaxe des décorateurs Python :
@deco
def somme(x,y):
return x+y
print(somme(2,3))
L’exécution du code.
10
Aussi, sachez qu’il est possible d’appliquer plusieurs décorateurs à une seule fonction.
2 commentaires
Akkal · 13 février 2022 à 18 h 12 min
Je suis avant la fonction
Bonjour #et non bonjour
Je suis après la fonction
pythonforge · 15 février 2022 à 3 h 37 min
Merci Akkal.