functools.wraps
在做什么。所以,我问这个问题,以便StackOverflow上有它的记录以备将来参考:functools.wraps
的作用是什么?#1 楼
使用装饰器时,您将一个功能替换为另一个。换句话说,如果您有装饰器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
。这将使用装饰器中使用的功能,并添加复制功能名称,文档字符串,参数列表等的功能。由于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'
#2 楼
我经常将类而不是函数用于装饰器。我遇到了一些麻烦,因为对象不会具有函数期望的所有相同属性。例如,一个对象将没有属性__name__
。我有一个特定的问题,很难跟踪Django报告错误“对象没有属性'__name__
'”的位置。不幸的是,对于类风格的装饰器,我认为@wrap不会胜任。相反,我创建了一个基本的装饰类,如下所示:因此,您现在可以创建一个简单的装饰器来检查是否指定了2个参数,如下所示: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)
评论
正如@wraps的文档所述,@ wraps只是functools.update_wrapper()的便捷功能。如果使用类装饰器,则可以直接从__init __()方法调用update_wrapper()。因此,您根本不需要创建DecBase,只需在process_log的__init __()上添加以下行:update_wrapper(self,func)。就这样。
–法比亚诺
19年2月13日在21:34
以便其他人也找到此答案:Flask及其add_url_route要求(在某些情况下?)所提供的view_func函数必须具有__name__,如果所提供的函数实际上是经过修饰的方法,则不再是这种情况,甚至在装饰器中使用functools.wraps时。
–乔尔
10月4日10:03
结果,为@Fabiano +1:使用update_wrapper而不是@wraps来完成工作:)
–乔尔
10月4日10:22
#3 楼
从python 3.5+开始:@functools.wraps(f)
def g():
pass
是
g = functools.update_wrapper(g, f)
的别名。它恰好完成三件事:它将
__module__
的__name__
,__qualname__
,__doc__
,__annotations__
和f
属性复制到g
上。此默认列表位于WRAPPER_ASSIGNMENTS
中,您可以在functools源代码中看到它。它将使用
__dict__
中的所有元素更新g
的f.__dict__
。 (请参见源代码中的WRAPPER_UPDATES
)。它在
__wrapped__=f
上设置了新的g
属性结果是
g
看起来具有相同的名称,文档字符串,模块名称和签名比f
唯一的问题是,关于签名,这实际上并不正确:只是默认情况下inspect.signature
遵循包装器链。您可以按照文档中的说明使用inspect.signature(g, follow_wrapped=False)
进行检查。这会带来恼人的后果:即使提供的参数无效,包装器代码也将执行。
包装器代码无法轻易从接收到的参数中使用其名称访问参数。 * args,** kwargs。实际上,将不得不处理所有情况(位置,关键字,默认值),因此要使用类似
Signature.bind()
的东西。现在
functools.wraps
和装饰器之间有些混乱,因为对于开发装饰器是为了包装功能。但是两者都是完全独立的概念。如果您有兴趣了解它们之间的区别,则可以为这两者实现帮助程序库:decopatch可以轻松编写装饰器,makemake可以为@wraps
提供保留签名的替代品。请注意,makefun
与著名的decorator
库依赖相同的可靠技巧。#4 楼
先决条件:您必须知道如何使用装饰器,尤其是包装纸。这个注释可以使它解释得很清楚,或者此链接也可以很好地说明它。根据此链接中给出的详细信息,它表示
functools.wraps是用于在定义包装函数时调用update_wrapper()作为函数装饰器的便捷函数。
它等同于partial(update_wrapper,wraped = wrapped,assigned = assigned,updated = updated)。 args] [,** keywords])。
functools.partial()定义表示
partial()用于部分函数应用程序,该应用程序“冻结”函数的某些部分自变量和/或关键字会导致具有简化签名的新对象。例如,partial()可用于创建行为类似于int()函数的可调用对象,该函数的基本参数默认为两个:
@wraps调用了partial(),并将包装函数作为参数传递给它。最后的partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。
#5 楼
这是有关wraps的源代码:WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__doc__')
WRAPPER_UPDATES = ('__dict__',)
def update_wrapper(wrapper,
wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Update a wrapper function to look like the wrapped function
wrapper is the function to be updated
wrapped is the original function
assigned is a tuple naming the attributes assigned directly
from the wrapped function to the wrapper function (defaults to
functools.WRAPPER_ASSIGNMENTS)
updated is a tuple naming the attributes of the wrapper that
are updated with the corresponding attribute from the wrapped
function (defaults to functools.WRAPPER_UPDATES)
"""
for attr in assigned:
setattr(wrapper, attr, getattr(wrapped, attr))
for attr in updated:
getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
# Return the wrapper so this can be used as a decorator via partial()
return wrapper
def wraps(wrapped,
assigned = WRAPPER_ASSIGNMENTS,
updated = WRAPPER_UPDATES):
"""Decorator factory to apply update_wrapper() to a wrapper function
Returns a decorator that invokes update_wrapper() with the decorated
function as the wrapper argument and the arguments to wraps() as the
remaining arguments. Default arguments are as for update_wrapper().
This is a convenience function to simplify applying partial() to
update_wrapper().
"""
return partial(update_wrapper, wrapped=wrapped,
assigned=assigned, updated=updated)
#6 楼
简而言之,functools.wraps只是一个常规函数。让我们考虑这个官方示例。在源代码的帮助下,我们可以看到有关实现和运行步骤的更多详细信息,如下所示:wraps(f)返回一个对象,例如O1。它是Partial类的对象。下一步是@ O1 ...,它是python中的修饰符。这意味着
wrapper = O1 .__ call __(wrapper)
检查__call__的实现,我们看到在此步骤之后,(左侧hand)wrapper成为self.func(* self.args,* args,** newkeywords)生成的对象。检查__new__中O1的创建,我们知道self.func是update_wrapper函数。它使用参数* args(右侧包装器)作为其第一个参数。检查update_wrapper的最后一步,可以看到返回了右侧包装,并根据需要修改了一些属性。
评论
是的,我更喜欢避免装饰器模块,因为functools.wraps是标准库的一部分,因此不会引入其他外部依赖关系。但是装饰器模块确实确实解决了帮助问题,希望有一天functools.wraps也可以。
– Eli Courtwright
09年11月25日在16:37
这是不使用自动换行会发生的情况的一个示例:doctools测试可能突然消失。这是因为除非诸如wraps()之类的东西复制过,否则doctools不能在修饰的函数中找到测试。
– andrew cooke
2011年4月24日在22:00
为什么我们需要functools.wraps来完成这项工作,首先它不应该只是装饰器模式的一部分吗?您何时不想使用@wraps?
– Wim
2014年1月2日在15:46
@wim:我编写了一些装饰器,它们执行自己的@wraps版本,以便对复制过来的值执行各种类型的修改或注释。从根本上讲,它是Python哲学的扩展,显式优于隐式,特殊情况不足以打破规则。 (如果必须手动提供@wraps,而不是使用某种特殊的选择退出机制,则代码会更简单,语言也更易于理解。)
– ssokolow
2014年3月29日在22:58
@LucasMalor并非所有的装饰器都包装它们装饰的功能。有些应用有副作用,例如在某种查找系统中注册它们。
– ssokolow
2015年7月23日在0:46