introduction aux décorateurs Python
Directory
sources
Introduction
Les décorateurs sont des wrappers, c’est à dire qu’ils permettent d’exécuter du code avant et après la fonction qu’ils décorent, sans modifier la fonction elle-même. Pour comprendre cela il faut prendre conscience de ce qu’implique le fait qu’une fonction soit un objet.
>>> def crier(mot="yes"):
...     return mot.capitalize() + "!"
>>> print(crier())
Yes!
Les fonctions sont des objets et peuvent être assignées à une variable:
À noter qu’on utilise pas les parenthèses. La fonction n’est pas applée. Ici nous mettons la fonction
crierdans la vriablehurlerafin de pouvoir appelercrieravechurler
>>> hurler = crier
>>> print(hurler())
Yes!
On peut également supprimer l’ancien nom crier, la fonction reste accessible avec hurler:
>>> del crier
>>> try:
...     print(crier())
...
>>> except NameError as e:
...     print(e)
...
name 'crier' is not defined
>>>print(hurler())
Yes!
Une fonctions peut être définie dans une autre fonction.
Ici, on appelle parler, qui définit chuchoter à chaque appel, puis chuchoter est appelé à l’intérieur de parler:
>>> def parler():
...     def chuchoter(mot="yes"):
...         return mot.lower() + "..."
...     print(chuchoter())
...
>>> parler()
yes...
Mais chuchoter n’existe pas endehors de parler:
>>> try:
...     print(chuchoter())
...
>>> except NameError as e:
...
>>> print(e)
name 'chuchoter' is not defined
Retour et Passage de fonctions par référence
Le fait que les fonctions peuvent être…
- assignées à une variable
- définies dans une autre fonction…
… implique qu’une fonctoin peut retourner une autre fonction:
noter l’absence de
()pour le retour. On appelle pas la fonction, on retourne l’objet fonction.
def creerParler(specification="crier"):
    def crier(mot="yes"):
        return mot.capitalize() + "!"
    def chuchoter(mot="yes"):
        return mot.lower() + "..."
    if specification == "crier":
        return crier
    else:
        return chuchoter
Obtenir la fonction et l’assigner à une variable:
>>> parler = creerParler()
parler est une variable qui contient la fonction crier:
>>> print(parler)
<function creerParler.<locals>.crier at 0x7f073ea62b70>
On peut appeler crier depuis parler:
>>> print(parler())
Yes!
Même chose avec la fonction chuchoter et sans utiliser de variable:
>>> print(creerParler("chuchoter"))
<function creerParler.<locals>.chuchoter at 0x7f9e743a4ea0>
>>> print(creerParler("chuchoter")())
yes...
Si on peut retourner une fonction, on peut églement en passer une en argument:
>>> def faireQqchAvant(fonction):
...     print("avant l'appel de la fonction")
...     print(fonction())
...
>>> faireQqchAvant(hurler)
avant l'appel de la fonction
Yes!
Décorateur fait maison
Un decorateur est une fonction qui attend une autre fonction en parametre:
def decorateur_perso(fonction_a_decorer):
    # En interne, decorateur_perso definit une
    # fonction a la volee: le wrapper.
    # Ce dernier va enrober la fonction originale
    # de telle sorte qu'il puisse executer du
    # code avant et pres celle-ci
    def wrapper():
        # Code qui s'execute AVANT que la 
        # fonction a decorer ne s'execute
        print("Avant que la fonction ne s'execute")
        # Appel de la fonction (avec les parentheses donc)
        fonction_a_decorer()
        # Code qui s'execute APRES que la
        # fonction a decorer se soit executee
        print("Apres l'execution")
    
    # Ici, la fonction a decorer N'A JAMAIS ETE EXECUTEE.
    # On retourne le wrapper (sans les parentheses donc) 
    # que l'on vient de creer.
    # Le wrapper contient la fonction originale et le code
    # a executer avant et apres, pret a etre utilise.
    return wrapper
Maintenant nous creons une fonction que l’on ne souhaite pas modifier.
def fonction_intouchable():
    print("Je suis une fonction intouchable !")
fonction_intouchable()
output:
Je suis une fonction intouchable !
Grace a decorateur_perso, nous pouvons étendre le comportement de la fonction_intouchable. Il suffit de la passer en argument au décorateur_perso qui va alors l’enrober dans le code que l’on souhaite, pour ensuite retourner une nouvelle fonction:
fonction_intouchable_decoree = decorateur_perso(fonction_intouchable)
fonction_intouchable_decoree()
output:
Avant que la fonction ne s'execute
Je suis une fonction intouchable !
Apres l'execution
Nous pouvons également faire en sorte qu’à chaque fois qu’on appelle fonction_intouchable, ce soit fonction_intouchable_decoree qui soit appelée. Il suffit d’écraser la fonction originale par celle retournée par le décorateur_perso:
fonction_intouchable = decorateur_perso(fonction_intouchable)
fonction_intouchable()
output:
Avant que la fonction ne s'execute
Je suis une fonction intouchable !
Apres l'execution
Voila ce que font les décorateurs.
Décorateur built-in
En reprenant l’exemple précédent, en utilisant la syntaxe précédente:
@decorateur_perso
def fonction_intouchable():
    print("Me touche pas !")
fonction_intouchable()
output:
Avant que la fonction ne s'execute
Je suis une fonction intouchable !
Apres l'execution
@decorateur_perso est simplement un raccourcis pour
fonction_intouchable = decorateur_perso(fonction_intouchable)
Les décorateurs sont juste une variante pythonique du classique patern design décorateur.
Nous pouvon cumuler ces derniers (syntaxe nulle):
def pain(func):
    def wrapper():
        print("</''''''\>")
        func()
        print("<\______/>")
    return wrapper
def ingredients(func):
    def wrapper():
        print("0tomates0")
        func()
        print("~salade~")
    return wrapper
@pain
@ingredients
def sandwitch(food="--jambon--"):
    print(food)
sandwitch()
output:
</''''''\>
0tomates0
--jambon--
~salade~
<\______/>
L’ordre d’application des décorateurs est important:
@ingredients
@pain
def sandwitch_nul(food="--jambon--"):
    print(food)
sandwitch_nul()
output:
0tomates0
</''''''\>
--jambon--
<\______/>
~salade~