What does functools.wraps do?
在对另一个问题的回答发表评论时,有人说他们不确定
当您使用装饰器时,您正在用另一个函数替换一个函数。换句话说,如果你有一个装饰师
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) |
您的函数
1 | print(f.__name__) |
它将打印
如果使用decorator总是意味着丢失有关函数的信息,那么这将是一个严重的问题。这就是为什么我们有
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' |
我经常为我的装饰师使用类,而不是函数。我在这方面遇到了一些问题,因为一个对象不会具有与期望的函数相同的所有属性。例如,对象将不具有属性
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 |
它是一
- 它的
__module__ __name__ ,复制,__qualname__ ,__doc__ 和__annotations__ f 在线g 属性)。这是一WRAPPER_ASSIGNMENTS 默认列表中,你可以看到它在functools源码。 - 它更新所有的
__dict__ g 从f.__dict__ )元素。国有企业在WRAPPER_UPDATES 源码) - 它的新的在线
g __wrapped__=f 属性集
那是在一个具有相同名称的
- 该包装器代码将运行参数是无效的,甚至当了。
- 该代码不能很容易地访问一个封装使用其名称的参数,从接收到kwargs***。的确,一个将要处理的所有案件(现有关键字,默认),因此
Signature.bind() 使用这样的事情。
现在,有一点混乱,因为
这是有关包装的源代码:
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只是一个常规函数。让我们考虑一下这个正式的例子。在源代码的帮助下,我们可以看到关于实现和运行步骤的更多详细信息,如下所示:
wrapper=O1.__call__(wrapper)
检查uu call_uuu的实现,我们看到在这个步骤之后,(左手边)包装器成为self.func(*self.args,*args,**newkeywords)检查在u new_uuuu中创建o1,我们知道self.func是函数更新包装器。它使用参数*args(右侧包装器)作为其第一个参数。检查update-wrapper的最后一步,可以看到返回了右侧的wrapper,并根据需要修改了一些属性。