functools.wraps是做什么的?

2022-09-05 01:18:28

在对另一个问题的答案的评论中,有人说他们不确定在做什么。所以,我问这个问题是为了在StackOverflow上有一个记录,以供将来参考:到底做了什么?functools.wrapsfunctools.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)

并且您的函数将替换为函数 。不幸的是,这意味着如果你然后说fwith_logging

print(f.__name__)

它将打印,因为这是您的新函数的名称。事实上,如果你看一下 docstring for ,它将是空白的,因为没有 docstring,所以你写的 docstr 将不再存在。另外,如果您查看该函数的pydoc结果,它不会被列为采用一个参数;相反,它将被列为采取,因为这是with_logging采取的。with_loggingfwith_loggingx*args**kwargs

如果使用装饰器总是意味着丢失有关函数的此信息,那将是一个严重的问题。这就是为什么我们有.这采用装饰器中使用的函数,并添加复制函数名称,文档字符串,参数列表等的功能。由于它本身就是一个装饰器,因此以下代码执行正确的操作:functools.wrapswraps

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

从python 3.5 +开始:

@functools.wraps(f)
def g():
    pass

是 的别名。它只做三件事:g = functools.update_wrapper(g, f)

  • 它复制 on 的 、 和属性。此默认列表位于 中,您可以在 functools 源代码中看到它。__module____name____qualname____doc____annotations__fgWRAPPER_ASSIGNMENTS
  • 它使用 中的所有元素更新 。(见来源)__dict__gf.__dict__WRAPPER_UPDATES
  • 它将新属性设置为__wrapped__=fg

结果是,看起来与 具有相同的名称、文档字符串、模块名称和签名。唯一的问题是,关于签名,这实际上不是真的:它只是默认遵循包装链。您可以使用文档中的说明进行检查。这会产生令人讨厌的后果:gfinspect.signatureinspect.signature(g, follow_wrapped=False)

  • 包装器代码将执行,即使提供的参数无效。
  • 包装器代码不能使用其名称轻松访问参数,从接收的 *args,**kwargs。实际上,必须处理所有情况(位置,关键字,默认值),因此使用类似.Signature.bind()

现在和装饰器之间有点混淆,因为开发装饰器的一个非常常见的用例是包装函数。但两者都是完全独立的概念。如果您有兴趣了解其中的区别,我为两者实现了帮助器库:decopatch可以轻松编写装饰器,并使makefun为.请注意,它依赖于与著名库相同的经过验证的技巧。functools.wraps@wrapsmakefundecorator