关于python:functools.wraps做什么?

What does functools.wraps do?

在对另一个问题的回答发表评论时,有人说他们不确定functools.wraps在做什么。所以,我问这个问题是为了在stackoverflow上有一个关于它的记录,以供将来参考:functools.wraps究竟做了什么?


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

1
2
3
4
5
def logged(func):
    def with_logging(*args, **kwargs):
        print(func.__name__ +" was called")
        return func(*args, **kwargs)
    return with_logging

当你说

1
2
3
4
@logged
def f(x):
  """does some math"""
   return x + x * x

这和说的完全一样

1
2
3
4
def f(x):
   """does some math"""
    return x + x * x
f = logged(f)

您的函数f被替换为函数"u logging"。不幸的是,这意味着如果你说

1
print(f.__name__)

它将打印with_logging,因为这是新函数的名称。实际上,如果您查看f的docstring,它将是空白的,因为with_logging没有docstring,所以您编写的docstring将不再存在。另外,如果您查看该函数的pydoc结果,它将不会被列为采用一个参数x;相反,它将被列为采用*args**kwargs,因为这是使用日志所需的。

如果使用decorator总是意味着丢失有关函数的信息,那么这将是一个严重的问题。这就是为什么我们有functools.wraps。这接受了修饰器中使用的函数,并添加了对函数名、docstring、参数列表等进行复制的功能。由于wraps本身就是一个修饰器,因此以下代码执行了正确的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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'


我经常为我的装饰师使用类,而不是函数。我在这方面遇到了一些问题,因为一个对象不会具有与期望的函数相同的所有属性。例如,对象将不具有属性__name__。我对此有一个特别的问题,很难追踪Django在哪里报告错误"object没有属性'__name__'"。不幸的是,对于类风格的装饰师来说,我不相信@wrap能做到这一点。我创建了一个这样的基础装饰类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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个参数:

1
2
3
4
5
6
class process_login(DecBase):
    def __call__(self, *args):
        if len(args) != 2:
            raise Exception("You can only specify two arguments")

        return self.func(*args)


Python的:3.5 +

1
2
3
@functools.wraps(f)
def g():
    pass

它是一g = functools.update_wrapper(g, f)别名。这三个东西是什么

  • 它的__module____name__,复制,__qualname____doc____annotations__f在线g属性)。这是一WRAPPER_ASSIGNMENTS默认列表中,你可以看到它在functools源码。
  • 它更新所有的__dict__gf.__dict__)元素。国有企业在WRAPPER_UPDATES源码)
  • 它的新的在线g__wrapped__=f属性集

那是在一个具有相同名称的g出现,模块名称和文档字符串,比f签名。唯一的问题是,这不是一个关于签名的真实:这是真的,只是默认为inspect.signature包装链。你可以检查它的利用inspect.signature(g, follow_wrapped=False)解释的DOC。这annoying后果:蛛网膜下腔出血(SAH)

  • 该包装器代码将运行参数是无效的,甚至当了。
  • 该代码不能很容易地访问一个封装使用其名称的参数,从接收到kwargs***。的确,一个将要处理的所有案件(现有关键字,默认),因此Signature.bind()使用这样的事情。

现在,有一点混乱,因为functools.wraps和decorators之间,使用非常频繁的情况下,开发decorators是包装的功能。但这两个概念是完全独立的。如果你有兴趣了解实现的差分对,是第一个辅助库:decopatch decorators易写,和makefun提供该签名是保持@wraps置换。请注意,makefunrelies在线的证明技巧decorator比著名的图书馆。


这是有关包装的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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)

  • 先决条件:你必须知道如何使用装饰,特别是与包装。这个评论解释得有点清楚,或者这个链接也解释得很好。

  • 每当我们使用例如:@wrapps后面跟着我们自己的wrapper函数时。根据这个链接中给出的细节,它说

  • functools.wraps is convenience function for invoking update_wrapper() as a function decorator, when defining a wrapper function.

    It is equivalent to partial(update_wrapper, wrapped=wrapped, assigned=assigned, updated=updated).

    所以@wrapps decorator实际上调用了functools.partial(func[,*args][,**keywords])。

    functools.partial()的定义表示

    The partial() is used for partial function application which"freezes" some portion of a function’s arguments and/or keywords resulting in a new object with a simplified signature. For example, partial() can be used to create a callable that behaves like the int() function where the base argument defaults to two:

    1
    2
    3
    4
    5
    >>> from functools import partial
    >>> basetwo = partial(int, base=2)
    >>> basetwo.__doc__ = 'Convert base 2 string to an int.'
    >>> basetwo('10010')
    18

    由此得出结论,@wrapps调用partial(),并将包装函数作为参数传递给它。最后的partial()返回简化版本,即包装函数内部的对象,而不是包装函数本身。


    简而言之,functools.wrapps只是一个常规函数。让我们考虑一下这个正式的例子。在源代码的帮助下,我们可以看到关于实现和运行步骤的更多详细信息,如下所示:

  • wrapps(f)返回一个对象,比如o1。它是分部类的对象
  • 下一步是@o1…这是Python中的装饰符号。它意味着
  • wrapper=O1.__call__(wrapper)

    检查uu call_uuu的实现,我们看到在这个步骤之后,(左手边)包装器成为self.func(*self.args,*args,**newkeywords)检查在u new_uuuu中创建o1,我们知道self.func是函数更新包装器。它使用参数*args(右侧包装器)作为其第一个参数。检查update-wrapper的最后一步,可以看到返回了右侧的wrapper,并根据需要修改了一些属性。