Dans un commentaire sur la réponse à une autre question, quelqu'un a dit qu’ils n’étaient pas sûrs de ce que functools.wraps a été fait. Donc je pose cette question afin qu’il y aura un enregistrement de celui-ci sur StackOverflow pour référence ultérieure : ce que functools.wraps ne fait, exactement ?
Réponses
Trop de publicités?Lorsque vous utilisez un décorateur, à remplacer 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
puis, quand vous dites
@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é par la fonction with_logging. Malheureusement, cela signifie que si vous puis dire
print f.__name__
il permet d'imprimer with_logging
car c'est le nom de votre nouvelle fonction. En fait, si vous regardez la docstring pour f, il sera vide car with_logging n'a pas de docstring, et donc la docstring vous avez écrit ne sera plus là. Aussi, si vous regardez la pydoc résultat pour cette fonction, il ne sera pas répertorié comme prenant un argument x
; au lieu de cela, il va être répertorié comme prenant *args
et **kwargs
parce que c'est ce with_logging prend.
Si à l'aide d'un décorateur toujours signifie la perte de cette information sur une fonction, il serait un sérieux problème. C'est pourquoi nous avons functools.wraps
. Cela prend une fonction utilisée dans un décorateur et ajoute les fonctionnalités de copier le nom de la fonction, docstring, les arguments de la liste, etc. Et depuis wraps
est lui-même un décorateur, le code suivant fonctionne correctement:
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'
J'ai très souvent l'utilisation de classes, plutôt que des fonctions, pour ma décorateurs. J'ai eu quelques problèmes avec cela à cause d'un objet n'aurez pas tous les mêmes attributs que l'on attend d'une fonction. Par exemple, un objet de ne pas avoir l'attribut __name__
. J'ai eu un problème spécifique avec ce qui était assez difficile de retrouver la trace où Django sur les rapports de l'erreur "objet n'a pas d'attribut '__name__
'". Malheureusement, pour la classe de style décorateurs, je ne crois pas que @wrap va faire le travail. J'ai plutôt créé une base de décorateur classe comme ceci:
class DecBase(object):
func = None
def __init__(self, func):
self.__func = func
def __getattribute__(self, name):
if name == "func":
return super(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def __setattr__(self, name, value):
if name == "func":
return super(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
Cette classe sert de proxy pour tous les attribut d'appels à la fonction qui est décoré. Ainsi, vous pouvez maintenant créer un simple décorateur qui vérifie que les 2 arguments sont spécifiés comme suit:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)