Comment puis-je créer deux décorateurs en Python qui feraient ce qui suit?
@makebold
@makeitalic
def say():
return "Hello"
... qui devrait retourner:
"<b><i>Hello</i></b>"
Je n'essaye pas de faire de HTML
cette façon dans une vraie application - j'essaie juste de comprendre comment les décorateurs et le chaînage des décorateurs fonctionnent.
__name__
et, en parlant du paquet décorateur, la signature de fonction).*args
et**kwargs
devrait être ajouté dans la réponse. La fonction décorée peut avoir des arguments et ils seront perdus s'ils ne sont pas spécifiés.*args
,**kwargs
. Un moyen facile de résoudre ces 3 problèmes à la fois est d'utiliserdecopatch
comme expliqué ici . Vous pouvez également utiliserdecorator
comme déjà mentionné par Marius Gedminas, pour résoudre les points 2 et 3.Si vous n'êtes pas dans les longues explications, voir la réponse de Paolo Bergantino .
Les bases du décorateur
Les fonctions de Python sont des objets
Pour comprendre les décorateurs, vous devez d'abord comprendre que les fonctions sont des objets en Python. Cela a des conséquences importantes. Voyons pourquoi avec un exemple simple:
Garde ça en tête. Nous y reviendrons sous peu.
Une autre propriété intéressante des fonctions Python est qu'elles peuvent être définies à l'intérieur d'une autre fonction!
Références des fonctions
D'accord, toujours là? Maintenant, la partie amusante ...
Vous avez vu que les fonctions sont des objets. Par conséquent, les fonctions:
Cela signifie qu'une fonction peut
return
une autre fonction .Il y a plus!
Si vous pouvez
return
une fonction, vous pouvez en passer une comme paramètre:Eh bien, vous avez juste tout ce qu'il faut pour comprendre les décorateurs. Vous voyez, les décorateurs sont des «wrappers», ce qui signifie qu'ils vous permettent d'exécuter du code avant et après la fonction qu'ils décorent sans modifier la fonction elle-même.
Décorateurs artisanaux
Comment vous le feriez manuellement:
Maintenant, vous voulez probablement que chaque fois que vous appelez
a_stand_alone_function
, ila_stand_alone_function_decorated
soit appelé à la place. C'est facile, il suffit d'écrasera_stand_alone_function
avec la fonction retournée parmy_shiny_new_decorator
:Les décorateurs démystifiés
L'exemple précédent, utilisant la syntaxe du décorateur:
Oui, c'est tout, c'est aussi simple que cela.
@decorator
est juste un raccourci vers:Les décorateurs ne sont qu'une variante pythonique du modèle de conception des décorateurs . Il existe plusieurs modèles de conception classiques intégrés à Python pour faciliter le développement (comme les itérateurs).
Bien sûr, vous pouvez accumuler des décorateurs:
Utilisation de la syntaxe du décorateur Python:
L'ordre dans lequel vous définissez les décorateurs est important:
Maintenant: pour répondre à la question ...
En conclusion, vous pouvez facilement voir comment répondre à la question:
Vous pouvez maintenant simplement partir heureux ou brûler un peu plus votre cerveau et voir les utilisations avancées des décorateurs.
Faire passer les décorateurs au niveau supérieur
Passer des arguments à la fonction décorée
Méthodes de décoration
Une chose intéressante à propos de Python est que les méthodes et les fonctions sont vraiment les mêmes. La seule différence est que les méthodes s'attendent à ce que leur premier argument soit une référence à l'objet courant (
self
).Cela signifie que vous pouvez créer un décorateur pour les méthodes de la même manière! N'oubliez pas de prendre
self
en considération:Si vous créez un décorateur à usage général - celui que vous appliquerez à n'importe quelle fonction ou méthode, peu importe ses arguments - alors utilisez simplement
*args, **kwargs
:Passer des arguments au décorateur
Très bien, que diriez-vous maintenant de passer des arguments au décorateur lui-même?
Cela peut être quelque peu tordu, car un décorateur doit accepter une fonction comme argument. Par conséquent, vous ne pouvez pas passer les arguments de la fonction décorée directement au décorateur.
Avant de nous précipiter vers la solution, écrivons un petit rappel:
C'est exactement pareil. "
my_decorator
" est appelé. Donc quand vous@my_decorator
, vous dites à Python d'appeler la fonction 'étiquetée par la variable "my_decorator
"'.C'est important! L'étiquette que vous donnez peut pointer directement vers le décorateur - ou non .
Obtenons le mal. ☺
Pas de surprise ici.
Faisons exactement la même chose, mais sautez toutes les variables intermédiaires embêtantes:
Rendons-le encore plus court :
Hé, tu as vu ça? Nous avons utilisé un appel de fonction avec la
@
syntaxe " "! :-)Alors, revenons aux décorateurs avec des arguments. Si nous pouvons utiliser des fonctions pour générer le décorateur à la volée, nous pouvons passer des arguments à cette fonction, non?
Le voici: un décorateur avec des arguments. Les arguments peuvent être définis comme variables:
Comme vous pouvez le voir, vous pouvez passer des arguments au décorateur comme n'importe quelle fonction en utilisant cette astuce. Vous pouvez même utiliser
*args, **kwargs
si vous le souhaitez. Mais rappelez-vous que les décorateurs ne sont appelés qu'une seule fois . Juste au moment où Python importe le script. Vous ne pouvez pas définir dynamiquement les arguments par la suite. Lorsque vous "importez x", la fonction est déjà décorée , vous ne pouvez donc rien changer.Pratiquons: décorer un décorateur
D'accord, en bonus, je vais vous donner un extrait pour que n'importe quel décorateur accepte génériquement n'importe quel argument. Après tout, afin d'accepter les arguments, nous avons créé notre décorateur en utilisant une autre fonction.
Nous avons enveloppé le décorateur.
Autre chose que nous avons vu récemment cette fonction enveloppée?
Oh oui, décorateurs!
Amusons-nous et écrivons un décorateur pour les décorateurs:
Il peut être utilisé comme suit:
Je sais, la dernière fois que vous avez eu ce sentiment, c'était après avoir écouté un gars qui disait: "avant de comprendre la récursivité, il faut d'abord comprendre la récursivité". Mais maintenant, ne vous sentez-vous pas bien de maîtriser cela?
Bonnes pratiques: décorateurs
Le
functools
module a été introduit dans Python 2.5. Il comprend la fonctionfunctools.wraps()
, qui copie le nom, le module et la docstring de la fonction décorée dans son wrapper.(Fait amusant:
functools.wraps()
est un décorateur! ☺)Comment les décorateurs peuvent-ils être utiles?
Maintenant, la grande question: à quoi puis-je utiliser des décorateurs?
Semble cool et puissant, mais un exemple pratique serait génial. Eh bien, il y a 1000 possibilités. Les utilisations classiques étendent le comportement d'une fonction à partir d'une bibliothèque externe (vous ne pouvez pas la modifier) ou pour le débogage (vous ne voulez pas la modifier car elle est temporaire).
Vous pouvez les utiliser pour étendre plusieurs fonctions à la manière d'un DRY, comme ceci:
Bien sûr, la bonne chose avec les décorateurs est que vous pouvez les utiliser immédiatement sur presque n'importe quoi sans réécriture. SEC, j'ai dit:
Python lui - même fournit plusieurs décorateurs:
property
,staticmethod
, etc.C'est vraiment une grande aire de jeux.
la source
__closure__
attribut) pour retirer la fonction non décorée d'origine. Un exemple d'utilisation est documenté dans cette réponse qui couvre comment il est possible d'injecter une fonction de décorateur à un niveau inférieur dans des circonstances limitées.@decorator
syntaxe de Python est probablement le plus souvent utilisée pour remplacer une fonction par une fermeture de wrapper (comme la réponse le décrit). Mais il peut également remplacer la fonction par autre chose. La commande interneproperty
,classmethod
etstaticmethod
décorateurs remplacent la fonction d'un descripteur, par exemple. Un décorateur peut également faire quelque chose avec une fonction, comme enregistrer une référence à celle-ci dans un registre quelconque, puis la renvoyer, sans modification, sans aucun wrapper.Vous pouvez également écrire une fonction d'usine qui renvoie un décorateur qui encapsule la valeur de retour de la fonction décorée dans une balise passée à la fonction d'usine. Par exemple:
Cela vous permet d'écrire:
ou
Personnellement, j'aurais écrit le décorateur un peu différemment:
ce qui donnerait:
N'oubliez pas la construction pour laquelle la syntaxe du décorateur est un raccourci:
la source
def wrap_in_tag(*kwargs)
alors@wrap_in_tag('b','i')
Il semble que les autres personnes vous aient déjà dit comment résoudre le problème. J'espère que cela vous aidera à comprendre ce que sont les décorateurs.
Les décorateurs ne sont que du sucre syntaxique.
Cette
s'étend à
la source
@decorator()
(au lieu de@decorator
) c'est du sucre syntaxique pourfunc = decorator()(func)
. C'est également une pratique courante lorsque vous devez générer des décorateurs "à la volée"Et bien sûr, vous pouvez également renvoyer des lambdas à partir d'une fonction de décoration:
la source
makebold = lambda f : lambda "<b>" + f() + "</b>"
makebold = lambda f: lambda: "<b>" + f() + "</b>"
makebold = lambda f: lambda *a, **k: "<b>" + f(*a, **k) + "</b>"
functools.wraps
afin de ne pas jeter la docstring / signature / nom desay
@wraps
un autre emplacement sur cette page ne m'aidera pas lorsque j'imprimeraihelp(say)
et obtiendrai "Aide sur la fonction <lambda>` au lieu de "Aide sur la fonction dire" .Les décorateurs Python ajoutent des fonctionnalités supplémentaires à une autre fonction
Un décorateur en italique pourrait être comme
Notez qu'une fonction est définie à l'intérieur d'une fonction. Ce qu'il fait, c'est remplacer une fonction par la fonction nouvellement définie. Par exemple, j'ai cette classe
Maintenant, disons que je veux que les deux fonctions affichent "---" après et avant qu'elles ne soient terminées. Je pourrais ajouter une impression "---" avant et après chaque instruction d'impression. Mais parce que je n'aime pas me répéter, je vais faire un décorateur
Alors maintenant, je peux changer ma classe en
Pour plus d'informations sur les décorateurs, consultez http://www.ibm.com/developerworks/linux/library/l-cpdecor.html
la source
self
argument est nécessaire car lenewFunction()
défini dans aaddDashes()
été spécifiquement conçu pour être un décorateur de méthode et non un décorateur de fonction générale. L'self
argument représente l'instance de classe et est transmis aux méthodes de classe, qu'elles l'utilisent ou non - voir la section intitulée Méthodes de décoration dans la réponse de @ e-satis.functools.wraps
Vous pouvez créer deux décorateurs distincts qui font ce que vous voulez, comme illustré ci-dessous. Notez l'utilisation de
*args, **kwargs
dans la déclaration de lawrapped()
fonction qui prend en charge la fonction décorée ayant plusieurs arguments (ce qui n'est pas vraiment nécessaire pour l'exemple desay()
fonction, mais est inclus pour la généralité).Pour des raisons similaires, le
functools.wraps
décorateur est utilisé pour modifier les méta-attributs de la fonction encapsulée pour être ceux de la décoration. Ainsi, les messages d'erreur et la documentation de fonction intégrée (func.__doc__
) sont ceux de la fonction décorée au lieu dewrapped()
.Raffinements
Comme vous pouvez le voir, il y a beaucoup de code en double dans ces deux décorateurs. Compte tenu de cette similitude, il serait préférable que vous en fassiez plutôt une générique qui était en fait une usine de décoration - en d'autres termes, une fonction de décoration qui fait d'autres décorateurs. De cette façon, il y aurait moins de répétition de code et permettrait au principe DRY d'être suivi.
Pour rendre le code plus lisible, vous pouvez attribuer un nom plus descriptif aux décorateurs générés en usine:
ou même les combiner comme ceci:
Efficacité
Bien que les exemples ci-dessus fonctionnent tous, le code généré implique une bonne quantité de surcharge sous la forme d'appels de fonction étrangers lorsque plusieurs décorateurs sont appliqués à la fois. Cela peut ne pas avoir d'importance, selon l'utilisation exacte (qui peut être liée aux E / S, par exemple).
Si la vitesse de la fonction décorée est importante, la surcharge peut être réduite à un seul appel de fonction supplémentaire en écrivant une fonction d'usine de décorateur légèrement différente qui implémente l'ajout de toutes les balises à la fois, afin de générer du code qui évite les appels de fonction supplémentaires encourus en utilisant des décorateurs séparés pour chaque étiquette.
Cela nécessite plus de code dans le décorateur lui-même, mais cela ne s'exécute que lorsqu'il est appliqué aux définitions de fonction, pas plus tard lorsqu'elles sont elles-mêmes appelées. Cela s'applique également lors de la création de noms plus lisibles à l'aide de
lambda
fonctions comme illustré précédemment. Échantillon:la source
Une autre façon de faire la même chose:
Ou, de manière plus flexible:
la source
functools.update_wrapper
pour gardersayhi.__name__ == "sayhi"
Vous voulez la fonction suivante, lorsqu'elle est appelée:
Rendre:
Solution simple
Pour le faire plus simplement, faites des décorateurs qui retournent des lambdas (fonctions anonymes) qui ferment la fonction (fermetures) et appelez-la:
Maintenant, utilisez-les comme vous le souhaitez:
et maintenant:
Problèmes avec la solution simple
Mais il semble que nous ayons presque perdu la fonction d'origine.
Pour le trouver, nous aurions besoin de creuser dans la fermeture de chaque lambda, dont l'un est enterré dans l'autre:
Donc, si nous mettons de la documentation sur cette fonction, ou si nous voulons pouvoir décorer des fonctions qui prennent plus d'un argument, ou si nous voulions simplement savoir quelle fonction nous regardions dans une session de débogage, nous devons faire un peu plus avec notre emballage.
Solution complète - surmonter la plupart de ces problèmes
Nous avons le décorateur
wraps
dufunctools
module dans la bibliothèque standard!Il est malheureux qu'il y ait encore du passe-partout, mais c'est à peu près aussi simple que nous pouvons le faire.
Dans Python 3, vous obtenez également
__qualname__
et vous êtes__annotations__
affecté par défaut.Alors maintenant:
Et maintenant:
Conclusion
Nous voyons donc que
wraps
la fonction d'habillage fait presque tout sauf nous dire exactement ce que la fonction prend comme arguments.Il existe d'autres modules qui peuvent tenter de résoudre le problème, mais la solution n'est pas encore dans la bibliothèque standard.
la source
Pour expliquer le décorateur de manière simple:
Avec:
Quand est-ce que:
Vous faites vraiment:
la source
Un décorateur prend la définition de la fonction et crée une nouvelle fonction qui exécute cette fonction et transforme le résultat.
est équivalent à:
Exemple:
Cette
est équivalent à cela
65 <=> 'a'
Pour comprendre le décorateur, il est important de noter que le décorateur a créé une nouvelle fonction do qui est intérieure qui exécute la fonction et transforme le résultat.
la source
print(do(65))
et ne devrait-elle pasprint(do2(65))
êtreA
etA
?Vous pouvez également écrire décorateur en classe
la source
Cette réponse a longtemps été répondue, mais je pensais que je partagerais ma classe de décorateur, ce qui rend l'écriture de nouveaux décorateurs facile et compacte.
D'une part, je pense que cela rend le comportement des décorateurs très clair, mais cela facilite également la définition très concise de nouveaux décorateurs. Pour l'exemple répertorié ci-dessus, vous pouvez alors le résoudre comme suit:
Vous pouvez également l'utiliser pour effectuer des tâches plus complexes, comme par exemple un décorateur qui applique automatiquement la fonction de manière récursive à tous les arguments d'un itérateur:
Qui imprime:
Notez que cet exemple n'a pas inclus le
list
type dans l'instanciation du décorateur, donc dans la déclaration d'impression finale, la méthode est appliquée à la liste elle-même, pas aux éléments de la liste.la source
Voici un exemple simple d'enchaîner des décorateurs. Notez la dernière ligne - elle montre ce qui se passe sous les couvertures.
La sortie ressemble à:
la source
En parlant de l'exemple de compteur - comme indiqué ci-dessus, le compteur sera partagé entre toutes les fonctions qui utilisent le décorateur:
De cette façon, votre décorateur peut être réutilisé pour différentes fonctions (ou utilisé pour décorer la même fonction plusieurs fois:)
func_counter1 = counter(func); func_counter2 = counter(func)
, et la variable compteur restera privée pour chacune.la source
Décorez les fonctions avec un nombre d'arguments différent:
Résultat:
la source
def wrapper(*args, **kwargs):
etfn(*args, **kwargs)
.La réponse de Paolo Bergantino a le grand avantage de n'utiliser que stdlib, et fonctionne pour cet exemple simple où il n'y a pas d' argument décorateur ni d' argument fonction décorée .
Cependant, il a 3 limitations majeures si vous souhaitez vous attaquer à des cas plus généraux:
makestyle(style='bold')
décorateur n'est pas anodine.@functools.wraps
ne conservent pas la signature , donc si de mauvais arguments sont fournis, ils commenceront à s'exécuter et pourraient générer un type d'erreur différent de l'habituel.TypeError
.@functools.wraps
d' accéder à un argument basé sur son nom . En effet, l'argument peut apparaître dans*args
, dans**kwargs
ou ne pas apparaître du tout (s'il est facultatif).J'ai écrit
decopatch
pour résoudre le premier problème et j'ai écrit pour résoudre lesmakefun.wraps
deux autres. Notez quemakefun
tire parti de la même astuce que la célèbredecorator
lib.Voici comment créer un décorateur avec des arguments, en retournant des wrappers préservant vraiment les signatures:
decopatch
vous propose deux autres styles de développement qui masquent ou affichent les différents concepts de python, selon vos préférences. Le style le plus compact est le suivant:Dans les deux cas, vous pouvez vérifier que le décorateur fonctionne comme prévu:
Veuillez vous référer à la documentation pour plus de détails.
la source