functools.wraps 有什么作用?
在对此另一个答案的评论中问题,有人说他们不确定 functools.wraps
在做什么。 所以,我问这个问题是为了在 StackOverflow 上记录它以供将来参考: functools.wraps
到底是做什么的?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(7)
当您使用装饰器时,您正在将一个函数替换为另一个函数。 换句话说,如果你有一个装饰器,
那么当你说它
与所说的完全相同
时,你的函数
f
就会被函数with_logging
替换。 不幸的是,这意味着如果您随后说它将打印
with_logging
因为这是您的新函数的名称。 事实上,如果您查看f
的文档字符串,它将是空白的,因为with_logging
没有文档字符串,因此您编写的文档字符串将不再存在。 另外,如果您查看该函数的 pydoc 结果,它不会被列为采用一个参数x
; 相反,它将被列为采用*args
和**kwargs
因为这就是 with_logging 所采用的。如果使用装饰器总是意味着丢失有关函数的信息,那么这将是一个严重的问题。 这就是我们有
functools.wraps
的原因。 这需要装饰器中使用的函数,并添加复制函数名称、文档字符串、参数列表等的功能。并且由于wraps
本身就是一个装饰器,因此以下代码执行正确的操作:When you use a decorator, you're replacing one function with another. In other words, if you have a decorator
then when you say
it's exactly the same as saying
and your function
f
is replaced with the functionwith_logging
. Unfortunately, this means that if you then sayit will print
with_logging
because that's the name of your new function. In fact, if you look at the docstring forf
, it will be blank becausewith_logging
has no docstring, and so the docstring you wrote won't be there anymore. Also, if you look at the pydoc result for that function, it won't be listed as taking one argumentx
; instead it'll be listed as taking*args
and**kwargs
because that's what with_logging takes.If using a decorator always meant losing this information about a function, it would be a serious problem. That's why we have
functools.wraps
. This takes a function used in a decorator and adds the functionality of copying over the function name, docstring, arguments list, etc. And sincewraps
is itself a decorator, the following code does the correct thing:从 python 3.5+ 开始:
是
g = functools.update_wrapper(g, f)
的别名。 它只做了三件事:__module__
、__name__
、__qualname__
、__doc__
和__annotations__
属性。 这个默认列表位于 WRAPPER_ASSIGNMENTS 中,您可以在 functools 源。g
上f
的f.__dict__
中的所有元素更新g
的__dict__
。 (请参阅源代码中的 WRAPPER_UPDATES )g
上设置新的__wrapped__=f
属性,结果是
g
显示为与f
具有相同的名称、文档字符串、模块名称和签名。 唯一的问题是,关于签名,这实际上不是真的:只是inspect.signature
默认情况下遵循包装器链。 您可以使用inspect.signature(g, follow_wrapped=False)
进行检查,如Signature.bind()
之类的东西。现在,functools.wraps 和装饰器之间存在一些混淆,因为开发装饰器的一个非常常见的用例是包装函数。 但两者是完全独立的概念。 如果您有兴趣了解差异,我为两者实现了帮助程序库: decopatch 来编写轻松装饰器,并 makefun 为
@wraps 提供保留签名的替代品. 请注意,
makefun
依赖于与著名的decorator
库相同的经过验证的技巧。As of python 3.5+:
Is an alias for
g = functools.update_wrapper(g, f)
. It does exactly three things:__module__
,__name__
,__qualname__
,__doc__
, and__annotations__
attributes off
ong
. This default list is inWRAPPER_ASSIGNMENTS
, you can see it in the functools source.__dict__
ofg
with all elements fromf.__dict__
. (seeWRAPPER_UPDATES
in the source)__wrapped__=f
attribute ong
The consequence is that
g
appears as having the same name, docstring, module name, and signature thanf
. The only problem is that concerning the signature this is not actually true: it is just thatinspect.signature
follows wrapper chains by default. You can check it by usinginspect.signature(g, follow_wrapped=False)
as explained in the doc. This has annoying consequences:Signature.bind()
.Now there is a bit of confusion between
functools.wraps
and decorators, because a very frequent use case for developing decorators is to wrap functions. But both are completely independent concepts. If you're interested in understanding the difference, I implemented helper libraries for both: decopatch to write decorators easily, and makefun to provide a signature-preserving replacement for@wraps
. Note thatmakefun
relies on the same proven trick than the famousdecorator
library.参考
Reference
我经常为我的装饰器使用类,而不是函数。 我在这方面遇到了一些麻烦,因为对象不会具有函数所期望的所有相同属性。 例如,对象不会具有属性
__name__
。 我遇到了一个具体问题,很难追踪 Django 报告错误“对象没有属性 '__name__
'”的位置。 不幸的是,对于类风格的装饰器,我不相信 @wrap 能完成这项工作。 相反,我创建了一个基本装饰器类,如下所示:该类将所有属性调用代理到正在装饰的函数。 因此,您现在可以创建一个简单的装饰器来检查是否指定了 2 个参数,如下所示:
I very often use classes, rather than functions, for my decorators. I was having some trouble with this because an object won't have all the same attributes that are expected of a function. For example, an object won't have the attribute
__name__
. I had a specific issue with this that was pretty hard to trace where Django was reporting the error "object has no attribute '__name__
'". Unfortunately, for class-style decorators, I don't believe that @wrap will do the job. I have instead created a base decorator class like so:This class proxies all the attribute calls over to the function that is being decorated. So, you can now create a simple decorator that checks that 2 arguments are specified like so:
先决条件:您必须知道如何使用装饰器,特别是包装器。 这个评论解释得有点清楚或者这个链接也很好地解释了它。
每当我们使用 For 例如:@wraps 后跟我们自己的包装函数。 根据此 链接 中给出的详细信息,它说
所以@wraps装饰器实际上调用了functools.partial(func[,*args][,**keywords])。
functools.partial() 定义表示
这使我得出结论,@wraps 调用了partial() 并传递了您的包装函数作为它的参数。 partial() 最后返回简化版本,即包装函数内部的对象,而不是包装函数本身。
Prerequisite: You must know how to use decorators and specially with wraps. This comment explains it a bit clear or this link also explains it pretty well.
Whenever we use For eg: @wraps followed by our own wrapper function. As per the details given in this link , it says that
So @wraps decorator actually gives a call to functools.partial(func[,*args][, **keywords]).
The functools.partial() definition says that
Which brings me to the conclusion that, @wraps gives a call to partial() and it passes your wrapper function as a parameter to it. The partial() in the end returns the simplified version i.e the object of what's inside the wrapper function and not the wrapper function itself.
这是关于包装的源代码:
this is the source code about wraps:
简而言之,functools.wraps只是一个常规函数。 让我们考虑一下这个官方示例。 借助源代码,我们可以有关实现和运行步骤的更多详细信息,如下所示:
检查 __call__,我们看到在这一步之后,(左侧)wrapper 变成了 self.func 结果的对象(*self.args, *args, **newkeywords) 检查__new__中O1的创建,我们知道self.func > 是函数update_wrapper。 它使用参数*args(右侧包装器)作为其第一个参数。 检查update_wrapper的最后一步,可以看到返回了右侧wrapper,并根据需要修改了一些属性。
In short, functools.wraps is just a regular function. Let's consider this official example. With the help of the source code, we can see more details about the implementation and the running steps as follows:
Checking the implementation of __call__, we see that after this step, (the left hand side )wrapper becomes the object resulted by self.func(*self.args, *args, **newkeywords) Checking the creation of O1 in __new__, we know self.func is the function update_wrapper. It uses the parameter *args, the right hand side wrapper, as its 1st parameter. Checking the last step of update_wrapper, one can see the right hand side wrapper is returned, with some of attributes modified as needed.