Py: Décorateurs
Py: Décorateurs

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 crier dans la vriable hurler afin de pouvoir appeler crier avec hurler

>>> 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~