Cet article a été mis à jour et contient maintenant du code Python en version 3

Les fonctions Python sont des objets

Pour comprendre les décorateurs, il faut d’abord comprendre que les fonctions sont des objets en Python. Cela a d’importantes conséquences:

def crier ( mot = "yes" ) : return mot. capitalize ( ) + "!" print ( crier ( ) ) # output : 'Yes!' # Puisque les fonctions sont des objets, # on peut les assigner à des variables hurler = crier # Notez que l'on n’utilise pas les parenthèses : # la fonction n'est pas appelée. Ici nous mettons la fonction "crier" # dans la variable "hurler" afin de pouvoir appeler "crier" avec "hurler" print ( hurler ( ) ) # output : 'Yes!' # Et vous pouvez même supprimer l'ancien nom "crier", # la fonction restera accessible avec "hurler" del crier try : print ( crier ( ) ) except NameError as e: print ( e ) #output: "name 'crier' is not defined" print ( hurler ( ) ) # output: 'Yes!' def crier(mot="yes"): return mot.capitalize() + "!" print(crier()) # output : 'Yes!' # Puisque les fonctions sont des objets, # on peut les assigner à des variables hurler = crier # Notez que l'on n’utilise pas les parenthèses : # la fonction n'est pas appelée. Ici nous mettons la fonction "crier" # dans la variable "hurler" afin de pouvoir appeler "crier" avec "hurler" print(hurler()) # output : 'Yes!' # Et vous pouvez même supprimer l'ancien nom "crier", # la fonction restera accessible avec "hurler" del crier try: print(crier()) except NameError as e: print(e) #output: "name 'crier' is not defined" print(hurler()) # output: 'Yes!'

Gardez ça à l’esprit, on va y revenir.

Une autre propriété intéressante des fonctions en Python est qu’on peut les définir à l’intérieur… d’une autre fonction.

def parler ( ) : # On peut définir une fonction à la volée dans "parler" ... def chuchoter ( mot = "yes" ) : return mot. lower ( ) + "..." ; # ... et l'utiliser immédiatement ! print ( chuchoter ( ) ) # On appelle "parler", qui définit "chuchoter" A CHAQUE APPEL, # puis "chuchoter" est appelé à l’intérieur de "parler" parler ( ) # output: # "yes..." # Mais "chuchoter" N'EXISTE PAS en dehors de "parler" try : print ( chuchoter ( ) ) except NameError , e: print ( e ) #output : "name 'chuchoter' is not defined" def parler(): # On peut définir une fonction à la volée dans "parler" ... def chuchoter(mot="yes"): return mot.lower()+"..."; # ... et l'utiliser immédiatement ! print(chuchoter()) # On appelle "parler", qui définit "chuchoter" A CHAQUE APPEL, # puis "chuchoter" est appelé à l’intérieur de "parler" parler() # output: # "yes..." # Mais "chuchoter" N'EXISTE PAS en dehors de "parler" try: print(chuchoter()) except NameError, e: print(e) #output : "name 'chuchoter' is not defined"

Passage des fonctions par référence

Toujours là ? Maintenant la partie amusante: vous avez vu que les fonctions sont des objets et peuvent donc:

être assignées à une variable;

être définies dans une autre fonction.

Cela veut dire aussi qu’une fonction peut retourner une autre fonction :-) Hop:

def creerParler ( type = "crier" ) : # On fabrique 2 fonctions à la volée def crier ( mot = "yes" ) : return mot. capitalize ( ) + "!" def chuchoter ( mot = "yes" ) : return mot. lower ( ) + "..." ; # Puis on retourne l'une ou l'autre if type == "crier" : # on utilise pas "()", on n’appelle pas la fonction # on retourne l'objet fonction return crier else : return chuchoter # Comment ce truc bizarre s'utilise ? # Obtenir la fonction et l'assigner à une variable parler = creerParler ( ) # "parler" est une variable qui contient la fonction "crier": print ( parler ) #output : <function crier at 0xb7ea817c> # On peut appeler "crier" depuis "parler": print ( parler ( ) ) #ouput : YES! # Et si on se sent chaud, on peut même créer et appeler la # fonction en une seule fois: print ( creerParler ( "chuchoter" ) ( ) ) #output : yes... def creerParler(type="crier"): # On fabrique 2 fonctions à la volée def crier(mot="yes"): return mot.capitalize() + "!" def chuchoter(mot="yes") : return mot.lower() + "..."; # Puis on retourne l'une ou l'autre if type == "crier": # on utilise pas "()", on n’appelle pas la fonction # on retourne l'objet fonction return crier else: return chuchoter # Comment ce truc bizarre s'utilise ? # Obtenir la fonction et l'assigner à une variable parler = creerParler() # "parler" est une variable qui contient la fonction "crier": print(parler) #output : <function crier at 0xb7ea817c> # On peut appeler "crier" depuis "parler": print(parler()) #ouput : YES! # Et si on se sent chaud, on peut même créer et appeler la # fonction en une seule fois: print(creerParler("chuchoter")()) #output : yes...

Mais c’est pas fini ! Si on peut retourner une fonction, on peut aussi en passer une en argument…

def faireQuelqueChoseAvant ( fonction ) : print ( "Je fais quelque chose avant d'appeler la fonction" ) print ( fonction ( ) ) faireQuelqueChoseAvant ( hurler ) #output: #Je fais quelque chose avant d'appeler la fonction #Yes! def faireQuelqueChoseAvant(fonction): print("Je fais quelque chose avant d'appeler la fonction") print(fonction()) faireQuelqueChoseAvant(hurler) #output: #Je fais quelque chose avant d'appeler la fonction #Yes!

C’est bon, vous avez toutes les cartes en main pour comprendre les décorateurs. En effet, 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.

Décorateur artisanal

Comment on en coderait un à la main:

# Un décorateur est une fonction qui attend une autre fonction en paramètre def decorateur_tout_neuf ( fonction_a_decorer ) : # En interne, le décorateur définit une fonction à la volée: le wrapper. # Le wrapper va enrober la fonction originale de telle sorte qu'il # puisse exécuter du code avant et après celle-ci def wrapper_autour_de_la_fonction_originale ( ) : # Mettre ici le code que l'on souhaite exécuter AVANT que la # fonction s’exécute print ( "Avant que la fonction ne s’exécute" ) # Apperler la fonction (en utilisant donc les parenthèses) fonction_a_decorer ( ) # Mettre ici le code que l'on souhaite exécuter APRES que la # fonction s’exécute print ( "Après que la fonction se soit exécutée" ) # Arrivé ici, la "fonction_a_decorer" n'a JAMAIS ETE EXECUTEE # On retourne le wrapper que l'on vient de créer. # Le wrapper contient la fonction originale et le code à exécuter # avant et après, prêt à être utilisé. return wrapper_autour_de_la_fonction_originale # Maintenant imaginez une fonction que l'on ne souhaite pas modifier. def une_fonction_intouchable ( ) : print ( "Je suis une fonction intouchable, on ne me modifie pas !" ) une_fonction_intouchable ( ) #output: Je suis une fonction intouchable, on ne me modifie pas ! # On peut malgré tout étendre son comportement # Il suffit de la passer au décorateur, qui va alors l'enrober dans # le code que l'on souhaite, pour ensuite retourner une nouvelle fonction une_fonction_intouchable_decoree = decorateur_tout_neuf ( une_fonction_intouchable ) une_fonction_intouchable_decoree ( ) #output: #Avant que la fonction ne s’exécute #Je suis une fonction intouchable, on ne me modifie pas ! #Après que la fonction se soit exécutée # Un décorateur est une fonction qui attend une autre fonction en paramètre def decorateur_tout_neuf(fonction_a_decorer): # En interne, le décorateur définit une fonction à la volée: le wrapper. # Le wrapper va enrober la fonction originale de telle sorte qu'il # puisse exécuter du code avant et après celle-ci def wrapper_autour_de_la_fonction_originale(): # Mettre ici le code que l'on souhaite exécuter AVANT que la # fonction s’exécute print("Avant que la fonction ne s’exécute") # Apperler la fonction (en utilisant donc les parenthèses) fonction_a_decorer() # Mettre ici le code que l'on souhaite exécuter APRES que la # fonction s’exécute print("Après que la fonction se soit exécutée") # Arrivé ici, la "fonction_a_decorer" n'a JAMAIS ETE EXECUTEE # On retourne le wrapper que l'on vient de créer. # Le wrapper contient la fonction originale et le code à exécuter # avant et après, prêt à être utilisé. return wrapper_autour_de_la_fonction_originale # Maintenant imaginez une fonction que l'on ne souhaite pas modifier. def une_fonction_intouchable(): print("Je suis une fonction intouchable, on ne me modifie pas !") une_fonction_intouchable() #output: Je suis une fonction intouchable, on ne me modifie pas ! # On peut malgré tout étendre son comportement # Il suffit de la passer au décorateur, qui va alors l'enrober dans # le code que l'on souhaite, pour ensuite retourner une nouvelle fonction une_fonction_intouchable_decoree = decorateur_tout_neuf(une_fonction_intouchable) une_fonction_intouchable_decoree() #output: #Avant que la fonction ne s’exécute #Je suis une fonction intouchable, on ne me modifie pas ! #Après que la fonction se soit exécutée

Puisqu’on y est, autant faire en sorte qu’à chaque fois qu’on appelle une_fonction_intouchable , c’est une_fonction_intouchable_decoree qui est appelée à la place. C’est facile, il suffit d’écraser la fonction originale par celle retournée par le décorateur :

une_fonction_intouchable = decorateur_tout_neuf ( une_fonction_intouchable ) une_fonction_intouchable ( ) #output: #Avant que la fonction ne s’exécute #Je suis une fonction intouchable, on ne me modifie pas ! #Après que la fonction se soit exécutée une_fonction_intouchable = decorateur_tout_neuf(une_fonction_intouchable) une_fonction_intouchable() #output: #Avant que la fonction ne s’exécute #Je suis une fonction intouchable, on ne me modifie pas ! #Après que la fonction se soit exécutée

Et c’est exactement ce que les décorateurs font.

Les décorateurs, démystifiés

L’exemple précédent, en utilisant la syntaxe précédente :

@ decorateur_tout_neuf def fonction_intouchable ( ) : print ( "Me touche pas !" ) fonction_intouchable ( ) #output: #Avant que la fonction ne s’exécute #Me touche pas ! #Après que la fonction se soit exécutée @decorateur_tout_neuf def fonction_intouchable(): print("Me touche pas !") fonction_intouchable() #output: #Avant que la fonction ne s’exécute #Me touche pas ! #Après que la fonction se soit exécutée

C’est tout. Oui, c’est aussi bête que ça.

@decorateur_tout_neuf est juste un raccourci pour

fonction_intouchable = decorateur_tout_neuf ( fonction_intouchable ) fonction_intouchable = decorateur_tout_neuf(fonction_intouchable)

Les décorateurs sont juste une variante pythonique du classique motif de conception “décorateur”.

Et bien sûr, on peut cumuler les décorateurs:

def pain ( func ) : def wrapper ( ) : print ( "</'''''' \> " ) func ( ) print ( "< \_ _____/>" ) return wrapper def ingredients ( func ) : def wrapper ( ) : print ( "#tomates#" ) func ( ) print ( "~salade~" ) return wrapper def sandwich ( food = "--jambon--" ) : print ( food ) sandwich ( ) #output: --jambon-- sandwich = pain ( ingredients ( sandwich ) ) sandwich ( ) #output: #</''''''\> # #tomates# # --jambon-- # ~salade~ #<\______/> def pain(func): def wrapper(): print("</''''''\>") func() print("<\______/>") return wrapper def ingredients(func): def wrapper(): print("#tomates#") func() print("~salade~") return wrapper def sandwich(food="--jambon--"): print(food) sandwich() #output: --jambon-- sandwich = pain(ingredients(sandwich)) sandwich() #output: #</''''''\> # #tomates# # --jambon-- # ~salade~ #<\______/>

Avec la syntaxe Python :

@ pain @ ingredients def sandwich ( nourriture = "--jambon--" ) : print ( nourriture ) sandwich ( ) #output: #</''''''\> # #tomates# # --jambon-- # ~salade~ #<\______/> @pain @ingredients def sandwich(nourriture="--jambon--"): print(nourriture) sandwich() #output: #</''''''\> # #tomates# # --jambon-- # ~salade~ #<\______/>

Avec cet exemple, on voit aussi que l’ordre d’application des décorateurs a de l’importance :

@ ingredients @ pain def sandwich_zarb ( nourriture = "--jambon--" ) : print ( nourriture ) sandwich_zarb ( ) #output: ##tomates# #</''''''\> # --jambon-- #<\______/> # ~salade~ @ingredients @pain def sandwich_zarb(nourriture="--jambon--"): print(nourriture) sandwich_zarb() #output: ##tomates# #</''''''\> # --jambon-- #<\______/> # ~salade~

Vous pouvez maintenant éteindre votre ordinateur et reprendre une activité normale.

Aller à la partie 2.