В комментарии к этому ответу на другой вопрос кто-то сказал, что не был уверен, что functools.wraps делает. Итак, я задаю этот вопрос, чтобы в StackOverflow была запись об этом для дальнейшего использования: что именно functools.wraps делает?
Переведено автоматически
Ответ 1
Когда вы используете декоратор, вы заменяете одну функцию другой. Другими словами, если у вас есть декоратор
@logged deff(x): """does some math""" return x + x * x
это точно то же самое, что сказать
deff(x): """does some math""" return x + x * x f = logged(f)
и ваша функция f заменяется функцией with_logging. К сожалению, это означает, что если вы затем скажете
print(f.__name__)
он будет напечатан with_logging потому что это имя вашей новой функции. На самом деле, если вы посмотрите на строку документа для f, она будет пустой, потому что with_logging не содержит строки документа, и поэтому строки документа, которую вы написали, там больше не будет. Кроме того, если вы посмотрите на результат pydoc для этой функции, он не будет указан как принимающий один аргумент x; вместо этого он будет указан как принимающий *args и **kwargs, потому что это то, что принимает with_logging .
Если бы использование декоратора всегда означало потерю этой информации о функции, это было бы серьезной проблемой. Вот почему у нас есть functools.wraps. Это принимает функцию, используемую в декораторе, и добавляет функциональность копирования имени функции, строки документации, списка аргументов и т.д. И поскольку wraps сам является декоратором, следующий код выполняет правильные действия:
from functools import wraps deflogged(func): @wraps(func) defwith_logging(*args, **kwargs): print(func.__name__ + " was called") return func(*args, **kwargs) return with_logging
@logged deff(x): """does some math""" return x + x * x
print(f.__name__) # prints 'f' print(f.__doc__) # prints 'does some math'
Ответ 2
Начиная с python 3.5+:
@functools.wraps(f) defg(): pass
Is an alias for g = functools.update_wrapper(g, f). It does exactly three things:
it copies the __module__, __name__, __qualname__, __doc__, and __annotations__ attributes of f on g. This default list is in WRAPPER_ASSIGNMENTS, you can see it in the functools source.
it updates the __dict__ of g with all elements from f.__dict__. (see WRAPPER_UPDATES in the source)
it sets a new __wrapped__=f attribute on g
The consequence is that g appears as having the same name, docstring, module name, and signature than f. The only problem is that concerning the signature this is not actually true: it is just that inspect.signature follows wrapper chains by default. You can check it by using inspect.signature(g, follow_wrapped=False) as explained in the doc. This has annoying consequences:
the wrapper code will execute even when the provided arguments are invalid.
the wrapper code can not easily access an argument using its name, from the received *args, **kwargs. Indeed one would have to handle all cases (positional, keyword, default) and therefore to use something like Signature.bind().
Now there is a bit of confusion between functools.wraps and decorators, because a very frequent use case for developing decorators is to wrap functions. But both are completely independent concepts. If you're interested in understanding the difference, I implemented helper libraries for both: decopatch to write decorators easily, and makefun to provide a signature-preserving replacement for @wraps. Note that makefun relies on the same proven trick than the famous decorator library.
Ответ 3
Assume we have this: Simple Decorator which takes a function’s output and puts it into a string, followed by three !!!!.
I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won't have all the same attributes that are expected of a function. For example, an object won't have the attribute __name__. I had a specific issue with this that was pretty hard to trace where Django was reporting the error "object has no attribute '__name__'". Unfortunately, for class-style decorators, I don't believe that @wrap will do the job. I have instead created a base decorator class like so:
classDecBase(object): func = None
def__init__(self, func): self.__func = func
def__getattribute__(self, name): if name == "func": returnsuper(DecBase, self).__getattribute__(name)
return self.func.__getattribute__(name)
def__setattr__(self, name, value): if name == "func": returnsuper(DecBase, self).__setattr__(name, value)
return self.func.__setattr__(name, value)
This class proxies all the attribute calls over to the function that is being decorated. So, you can now create a simple decorator that checks that 2 arguments are specified like so:
classprocess_login(DecBase): def__call__(self, *args): iflen(args) != 2: raise Exception("You can only specify two arguments")