functools.wraps 有什么作用?

新手上路,请多包涵

对另一个问题的这个答案 的评论中,有人说他们不确定 functools.wraps 在做什么。所以,我问这个问题,以便在 StackOverflow 上有它的记录以供将来参考: functools.wraps 究竟是做什么的?

原文由 Eli Courtwright 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 498
2 个回答

当您使用装饰器时,您是在用另一个函数替换一个函数。换句话说,如果你有一个装饰器

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'

原文由 Eli Courtwright 发布,翻译遵循 CC BY-SA 4.0 许可协议

从 python 3.5+ 开始:

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

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

  • it copies the __module__ , __name__ , __qualname__ , __doc__ , and __annotations__ attributes of fg 上。此默认列表在 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 可以轻松编写装饰器, makefun 可以为 @wraps 提供签名保留替代品。请注意, makefun 依赖于与著名的 decorator 库相同的经过验证的技巧。

原文由 smarie 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题