functools.wraps有什么作用?

时间:2008-11-21 14:53:41

标签: python decorator functools

在对此answer to another question的评论中,有人说他们不确定functools.wraps正在做什么。所以,我问这个问题,以便在StackOverflow上有一个记录,以供将来参考:functools.wraps做了什么,确切地说?

6 个答案:

答案 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进行检查。这会带来恼人的后果:

  • 即使提供的参数无效,包装器代码也将执行。
  • 包装器代码无法轻松地从接收到的* args,** kwargs中使用其名称访问参数。确实,必须处理所有情况(位置,关键字,默认),并因此使用类似inspect.signature(g, follow_wrapped=False)的方法。

现在Signature.bind()和装饰器之间有些混乱,因为开发装饰器的一个非常常见的用例是包装函数。但是两者都是完全独立的概念。如果您有兴趣了解它们之间的区别,则可以为这两种方法实现帮助程序库:decopatch可以轻松编写修饰符,makefun可以为functools.wraps提供保留签名的替代方法。请注意,@wraps与著名的makefun库依赖相同的可靠技巧。

答案 4 :(得分:1)

  1. 先决条件:您必须知道如何使用装饰器,特别是包装。这个comment解释得有点清楚,或link也很好地解释了它。

  2. 每当我们使用For例如:@wraps后跟我们自己的包装函数。根据此link中提供的详细信息,它表示

  3.   

    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的帮助下,我们可以看到有关实施和运行步骤的更多详细信息,如下所示:

  1. 换行(f)会返回一个对象,比如 O1 。它是class Partial
  2. 的对象
  3. 下一步是 @ O1 ... ,这是python中的装饰符号。这意味着
  4.   

    <强>包装= O1 .__呼叫__(包装)

    检查__call__的实现,我们看到在此步骤之后,(左侧)包装器成为 self.func(* self.args)导致的对象,* args,** newkeywords)检查 __ new __ O1 的创建,我们知道 self.func 是函数<强> update_wrapper 即可。它使用参数 * args ,右侧包装作为其第一个参数。检查 update_wrapper 的最后一步,可以看到返回右侧包装器,并根据需要修改了一些属性。