在对此answer to another question的评论中,有人说他们不确定functools.wraps
正在做什么。所以,我问这个问题,以便在StackOverflow上有一个记录,以供将来参考:functools.wraps
做了什么,确切地说?
答案 0 :(得分:889)
当您使用装饰器时,您将一个功能替换为另一个功能。换句话说,如果你有一个装饰者
def logged(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
与说
完全相同def f(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
的原因。这需要在装饰器中使用的函数,并添加复制函数名,docstring,参数列表等的功能。由于wraps
本身就是一个装饰器,下面的代码做了正确的事情:
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'
答案 1 :(得分:20)
我经常为我的装饰者使用类而不是函数。我遇到了一些麻烦,因为一个对象不具有与函数相同的所有属性。例如,对象不具有属性__name__
。我有一个特定的问题,很难追踪Django报告错误“对象没有属性'__name__
'”的地方。不幸的是,对于类式装饰器,我不相信@wrap会完成这项工作。我改为创建了一个基础装饰器类:
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)
此类将所有属性调用代理到正在装饰的函数。因此,您现在可以创建一个简单的装饰器来检查指定2个参数,如下所示:
class process_login(DecBase):
def __call__(self, *args):
if len(args) != 2:
raise Exception("You can only specify two arguments")
return self.func(*args)
答案 2 :(得分:3)
这是关于wraps的源代码:
JSON.parse
答案 3 :(得分:3)
从python 3.5+开始:
@functools.wraps(f)
def g():
pass
是g = functools.update_wrapper(g, f)
的别名。它确实完成了三件事:
__module__
上复制__name__
的{{1}},__qualname__
,__doc__
,__annotations__
和f
属性。此默认列表位于g
中,您可以在functools source中看到它。WRAPPER_ASSIGNMENTS
中的所有元素更新__dict__
中的g
。 (请参见源中的f.__dict__
)WRAPPER_UPDATES
上设置了一个新的__wrapped__=f
结果是g
的名称,文档字符串,模块名称和签名与g
相同。唯一的问题是,关于签名,这实际上并不正确:只是f
默认遵循包装器链。您可以按照doc中的说明使用inspect.signature
进行检查。这会带来恼人的后果:
inspect.signature(g, follow_wrapped=False)
的方法。现在Signature.bind()
和装饰器之间有些混乱,因为开发装饰器的一个非常常见的用例是包装函数。但是两者都是完全独立的概念。如果您有兴趣了解它们之间的区别,则可以为这两种方法实现帮助程序库:decopatch可以轻松编写修饰符,makefun可以为functools.wraps
提供保留签名的替代方法。请注意,@wraps
与著名的makefun
库依赖相同的可靠技巧。
答案 4 :(得分:1)
每当我们使用For例如:@wraps后跟我们自己的包装函数。根据此link中提供的详细信息,它表示
functools.wraps是在定义包装函数时调用update_wrapper()作为函数装饰器的便捷函数。
它相当于partial(update_wrapper,wrapped = wrapped,assigned = assigned,updated = updated)。
所以@wraps装饰器实际上调用了functools.partial(func [,* args] [,** keywords])。
functools.partial()定义说
partial()用于部分函数应用程序,它“冻结”函数参数和/或关键字的某些部分,从而产生具有简化签名的新对象。例如,partial()可用于创建一个callable,其行为类似于int()函数,其中base参数默认为2:
{{1}}
这让我得出结论:@wraps调用partial()并将包装器函数作为参数传递给它。最后的partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。
答案 5 :(得分:-4)
简而言之, functools.wraps 只是一个常规功能。我们来考虑this official example。在source code的帮助下,我们可以看到有关实施和运行步骤的更多详细信息,如下所示:
<强>包装= O1 .__呼叫__(包装)强>
检查__call__的实现,我们看到在此步骤之后,(左侧)包装器成为 self.func(* self.args)导致的对象,* args,** newkeywords)检查 __ new __ 中 O1 的创建,我们知道 self.func 是函数<强> update_wrapper 即可。它使用参数 * args ,右侧包装作为其第一个参数。检查 update_wrapper 的最后一步,可以看到返回右侧包装器,并根据需要修改了一些属性。