Lorsque vous utilisez un décorateur, vous remplacez une fonction par une autre. En d'autres termes, si vous avez un décorateur
def logged(func):
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
alors quand tu dis
@logged
def f(x):
"""does some math"""
return x + x * x
c'est exactement la même chose que de dire
def f(x):
"""does some math"""
return x + x * x
f = logged(f)
et votre fonction f
est remplacée par la fonction with_logging
. Malheureusement, cela signifie que si vous dites
print(f.__name__)
il s'imprimera with_logging
car c'est le nom de votre nouvelle fonction. En fait, si vous regardez la docstring f
, elle sera vide car with_logging
n'a pas de docstring, et donc la docstring que vous avez écrite ne sera plus là. De plus, si vous regardez le résultat pydoc pour cette fonction, il ne sera pas répertorié comme prenant un seul argument x
; au lieu de cela, il sera répertorié comme prenant *args
et **kwargs
parce que c'est ce que prend with_logging.
Si utiliser un décorateur signifiait toujours perdre ces informations sur une fonction, ce serait un problème grave. C'est pourquoi nous l'avons fait functools.wraps
. Cela prend une fonction utilisée dans un décorateur et ajoute la fonctionnalité de copie sur le nom de la fonction, la docstring, la liste des arguments, etc. Et comme wraps
c'est lui-même un décorateur, le code suivant fait la bonne chose:
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + " was called")
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
print(f.__name__) # prints 'f'
print(f.__doc__) # prints 'does some math'
functools.wraps
pour ce travail, ne devrait-il pas simplement faire partie du motif de décoration en premier lieu? quand ne voudriez-vous pas utiliser @wraps?@wraps
afin d'effectuer différents types de modifications ou d'annotations sur les valeurs copiées. Fondamentalement, c'est une extension de la philosophie Python qu'explicite vaut mieux qu'implicite et les cas spéciaux ne sont pas assez spéciaux pour enfreindre les règles. (Le code est beaucoup plus simple et le langage plus facile à comprendre s'il@wraps
doit être fourni manuellement, plutôt que d'utiliser une sorte de mécanisme spécial de désinscription.)J'utilise très souvent des classes, plutôt que des fonctions, pour mes décorateurs. J'avais quelques problèmes avec cela car un objet n'aura pas tous les mêmes attributs que ceux attendus d'une fonction. Par exemple, un objet n'aura pas l'attribut
__name__
. J'ai eu un problème spécifique avec cela qui était assez difficile à trouver où Django rapportait l'erreur "l'objet n'a pas d'attribut"__name__
"". Malheureusement, pour les décorateurs de classe, je ne pense pas que @wrap fera l'affaire. J'ai plutôt créé une classe de décorateur de base comme ceci:Cette classe envoie par proxy tous les appels d'attribut à la fonction en cours de décoration. Ainsi, vous pouvez maintenant créer un décorateur simple qui vérifie que 2 arguments sont spécifiés comme suit:
la source
@wraps
dit la documentation de ,@wraps
c'est juste une fonction pratique pourfunctools.update_wrapper()
. En cas de décorateur de classe, vous pouvez appelerupdate_wrapper()
directement depuis votre__init__()
méthode. Donc, vous n'avez pas besoin de créerDecBase
du tout, vous pouvez simplement inclure sur__init__()
deprocess_login
la ligne:update_wrapper(self, func)
. C'est tout.Depuis python 3.5+:
Est un alias pour
g = functools.update_wrapper(g, f)
. Il fait exactement trois choses:__module__
,__name__
,__qualname__
,__doc__
et__annotations__
attributs def
surg
. Cette liste par défaut estWRAPPER_ASSIGNMENTS
dedans, vous pouvez la voir dans la source functools .__dict__
deg
tous les éléments def.__dict__
. (voirWRAPPER_UPDATES
dans la source)__wrapped__=f
attribut surg
La conséquence est qu'il
g
apparaît comme ayant le même nom, docstring, nom de module et signature quef
. Le seul problème est qu'en ce qui concerne la signature, ce n'est pas vrai: c'est juste queinspect.signature
suit les chaînes de wrapper par défaut. Vous pouvez le vérifier en utilisantinspect.signature(g, follow_wrapped=False)
comme expliqué dans la doc . Cela a des conséquences gênantes:Signature.bind()
.Maintenant, il y a un peu de confusion entre
functools.wraps
et les décorateurs, car un cas d'utilisation très fréquent pour développer des décorateurs est d'encapsuler des fonctions. Mais les deux sont des concepts complètement indépendants. Si vous êtes intéressé à comprendre la différence, j'ai implémenté des bibliothèques d'aide pour les deux: decopatch pour écrire facilement les décorateurs et makefun pour fournir un remplacement préservant la signature@wraps
. Notez quemakefun
repose sur la même astuce éprouvée que la célèbredecorator
bibliothèque.la source
voici le code source des wraps:
la source
Prérequis: Vous devez savoir utiliser des décorateurs et spécialement avec des wraps. Ce commentaire l' explique un peu clair ou ce lien l' explique aussi assez bien.
Chaque fois que nous utilisons Pour par exemple: @wraps suivi de notre propre fonction wrapper. Selon les détails donnés dans ce lien , il est dit que
Donc, @wraps decorator appelle en fait functools.partial (func [, * args] [, ** mots-clés]).
La définition de functools.partial () dit que
Ce qui m'amène à la conclusion que @wraps donne un appel à partial () et lui transmet votre fonction wrapper comme paramètre. Le partial () à la fin renvoie la version simplifiée, c'est-à-dire l'objet de ce qui est à l'intérieur de la fonction wrapper et non la fonction wrapper elle-même.
la source
En bref, functools.wraps n'est qu'une fonction régulière. Prenons cet exemple officiel . Avec l'aide du code source , nous pouvons voir plus de détails sur l'implémentation et les étapes en cours comme suit:
En vérifiant l'implémentation de __call__ , nous voyons qu'après cette étape, (le côté gauche) le wrapper devient l'objet résultant par self.func (* self.args, * args, ** newkeywords) Vérification de la création de O1 dans __new__ , nous connaître self.func est la fonction update_wrapper . Il utilise le paramètre * args , le wrapper de droite , comme son premier paramètre. En vérifiant la dernière étape de update_wrapper , on peut voir que le wrapper de droite est retourné, avec certains attributs modifiés selon les besoins.
la source