Thématiques principales

samedi 9 mars 2019

Python : Les décorateurs

Bien nous voilà maintenant encore un nouveau sujet Python, les décorateurs ! Alors promis c’est le dernier! (ou presque) parce qu'après on s'intéressera à des aspects plutôt technologique de python plutôt que des subtilités du langages…

On va donc tâcher de faire court afin de ne pas lasser!

Les décorateurs

Vous connaissez le pattern décorateur ? et bien c’est un peu la même idée sauf que dans notre cas le but est de faire plutôt comme le pattern Proxy…

Quoi? Vous comprenez pas? Ok faisons un parallèle avec Java… Dans Java lorsque l’on dispose d’une classe comportant certains comportement mais que ces derniers ne suffisent pas, on peut employer ce que l’on appelle un proxy [pattern-prox].

Dans le principe le proxy va prendre en paramètre l’objet que l’on souhaite décorer et s’enregistrer auprès de ce dernier afin de déclencher l’une de ses méthodes en lieu et place de l’objet en lui même. Alors bien sûr, souvent on joue quand même la méthode de l'objet mais celui ci on ajoute quelques comportement supplémentaire, on dit que l’on va décorer…

Bien en python c’est un peu la même idée mais avec des fonctions.

Ainsi un décorateur est une fonction prenant en paramètre une autre fonction (celle ci est dite décorée). Le déclarant est exécuté sur la fonction dès sa déclaration et permet donc d’agir sur celle ci.




def monDecorateur(func):
    print("Je decore la fonction ", func)
    return func
  
@monDecorateur
def uneFonction():
    print("je m'execute")


Je decore la fonction  <function uneFonction at 0x00DEEFA8>

OK le décorateur est exécuté sur la définition de la fonction décorée (et non lors de son véritable appel) mais pourquoi le décorateur renvoie cette même fonction?

Et bien parce que le décorateur va participer à la construction de la fonction et celle retourné et le résultat de son travail sur la fonction lors de sa définition.

Donc… en tout logique on pourrait retourner une autre fonction que celle passée en paramètre?

Et bien oui et c’est d’ailleurs l’idée voir même d’aller plus loin en définissant dans le décorateur une fonction que l’on retournera à la place de celle de la définition mais qui encapsule cette dernière avec quelques plus:


def monDecorateur(func):
    print("Je decore la fonction ", func)
    def maNouvelleFonction():
        print("j'execute la nouvelle fonction en plus")
        func()
    
    return maNouvelleFonction
  
@monDecorateur
def uneFonction():
    print("je m'execute")
    
uneFonction()


Je decore la fonction  <function uneFonction at 0x083AE348>
j'execute la nouvelle fonction en plus
je m'execute


Bien on a vue le minimum sur les décorateurs, maintenant allons plus loin car on peut se demander si il est possible par exemple de substituer la fonction par une fonction externe à laquelle on donnerait en paramètre notre fonction… et  la non c’est pas possible car il faudrait que l’on puisse passer en paramètre notre fonction à décorer à la fonction de substitution sans l'exécuter… et ça c’est pas possible de cette manière. La définition de la fonction de substitution doit être réalisé à l'intérieur du décorateur.

Mais alors comment faire si la fonction à substituer possède des paramètres et comment faire si l’on souhaite paramétrer le décorateur? Et bien regardons cela.


def monDecorateur(valeur):
    print("monDecorateur prend la valeur",valeur)
    def decorateur(func):
        print("Je decore la fonction ", func)
        def maNouvelleFonction():
            print("j'execute la nouvelle fonction en plus")
            func()
        return maNouvelleFonction
    return decorateur
  
@monDecorateur(2)
def uneFonction():
    print("je m'execute")
    
uneFonction()

print("#############")

def uneAutreFonction():
    print("je m'execute")

monDecorateur(2)(uneAutreFonction)()


monDecorateur prend la valeur 2
Je decore la fonction  <function uneFonction at 0x0322AD20>
j'execute la nouvelle fonction en plus
je m'execute
#############
monDecorateur prend la valeur 2
Je decore la fonction  <function uneAutreFonction at 0x0322A738>
j'execute la nouvelle fonction en plus
je m'execute

Ainsi voilà si l’on souhaite passer des paramètres à notre décorateur, alors que celui ci est une fonction prenant en paramètre une valeur, il va falloir composer. Par composer, je signifie justement qu’il va falloir jouer sur l’encapsulation de définition de fonction par des fonction retournant les dites fonctions. Ainsi de cette manière on simule un appel à une fonction possédant différents modes d’appel. Comme présenté par le code équivalent appliqué à uneAutreFonction


def monDecorateur(valeur):
    print("monDecorateur prend la valeur",valeur)
    def decorateur(func):
        print("Je decore la fonction ", func)
        def maNouvelleFonction(parametre):
            print("j'execute la nouvelle fonction en plus et ca me permet de filtrer")
            func(parametre)
        return maNouvelleFonction
    return decorateur
  
@monDecorateur(2)
def uneFonction(parametre):
    print("je m'execute avec des param",parametre)
    
uneFonction("prout quoi!")


monDecorateur prend la valeur 2
Je decore la fonction  <function uneFonction at 0x0322A2B8>
j'execute la nouvelle fonction en plus
je m'execute avec des param prout quoi!


Enfin de la même manière pour le passage des paramètres, comme vous l’avez compris par le mécanisme de la composition des définitions, le report du passage de paramètre se réalise sur la fonction encapsulante finale. De même le code équivalent est présenté et appliqué à uneAutreFonction

Voilà le cas des décorateurs est traités, il est très intéressant car il amène un concept de la programmation que l’on voit peut avec des langages comme Java ou C++ où la réification du langage ne va pas jusqu’au tout objet et qui permet ce genre de structure. Cela nous donnera un avant goût de JavaScript où cette technique est largement employée et généralisée.

A noter, même si l'on en a pas parler, que les décorateurs s'appliquent également sur des classes... mais c'est une autre histoire

Références

[pattern-prox] https://un-est-tout-et-tout-est-un.blogspot.com/2017/11/design-pattern-proxy.html

Aucun commentaire:

Enregistrer un commentaire