问题:functools.wraps是做什么的?
在对另一个问题的答案发表评论时,有人说他们不确定functools.wraps
在做什么。所以,我问这个问题,以便在StackOverflow上有它的记录,以备将来参考:到底是functools.wraps
做什么的?
回答 0
使用装饰器时,您将一个功能替换为另一个功能。换句话说,如果您有一个装饰器
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
将替换为function 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'
回答 1
我经常将类而不是函数用于装饰器。我遇到了一些麻烦,因为对象将不具有函数期望的所有相同属性。例如,对象将没有属性__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
从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__
of 。(看到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可以轻松编写装饰器,而makefun可以提供保留签名的替代方法@wraps
。注意,它makefun
依赖于与著名decorator
库相同的可靠技巧。
回答 3
这是关于包装的源代码:
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)
回答 4
每当我们使用For例如:@wraps后跟我们自己的包装函数。根据此链接中给出的详细信息,它说
functools.wraps是方便函数,用于在定义包装函数时调用update_wrapper()作为函数装饰器。
它等效于partial(update_wrapper,wraped = wrapped,assigned = assigned,updated = updated)。
因此,@ wraps装饰器实际上会调用functools.partial(func [,* args] [,** keywords])。
functools.partial()定义说
partial()用于部分函数应用程序,该函数“冻结”函数的参数和/或关键字的某些部分,从而生成具有简化签名的新对象。例如,partial()可用于创建行为类似于int()函数的可调用对象,其中基本参数默认为两个:
>>> from functools import partial
>>> basetwo = partial(int, base=2)
>>> basetwo.__doc__ = 'Convert base 2 string to an int.'
>>> basetwo('10010')
18
这使我得出以下结论:@wraps调用了partial(),并将包装函数作为参数传递给它。最后,partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。
回答 5
简而言之,functools.wraps只是一个常规函数。让我们考虑这个官方示例。借助源代码,我们可以看到有关实现和运行步骤的更多详细信息,如下所示:
- wraps(f)返回一个对象,例如O1。它是Partial类的对象
- 下一步是@ O1 …,这是python中的修饰符符号。它的意思是
wrapper = O1 .__ call __(wrapper)
检查的执行__call__,我们看到这一步,(左侧)后包装成为对象造成的self.func(* self.args,* ARGS,** newkeywords)检查创建O1在__new__,我们知道self.func是功能update_wrapper。它使用参数* args(右侧包装器)作为其第一个参数。检查update_wrapper的最后一步,可以看到返回了右侧包装器,并根据需要修改了一些属性。