请修复你的装饰器
请修复你的装饰器,如果你的Python装饰器无意间改变了我的可调用对象的签名或者对于类方法不起作用,那么它就是坏了,需要修复。不幸的是,大多数装饰器都是坏的,因为网上充斥着不好的建议。
(如果你仅仅想知道怎么做,请直接跳到结论)
快速回顾
一个函数装饰器1是封装可调用对象(即函数或方法)来改变他们行为的一种方法。通常是因为需要在封装对象之前并且/或者之后来执行一些代码、修改参数或者返回一些值、缓存等等。
他们本身便是应用于封装函数的可调用对象:
由于这是一个很常见的模式,因而Python为它准备了一个语法糖,因而:
与上边的代码含义相同。
现在让我们看看Python文档中的例子:
…看看哪里出问题了?我们定义一个函数,它可以将两个数相乘,并且可以接收一个默认值为1的第三个乘积因子:
问题
损坏的签名
让我们利用inspect检查mult函数:
你可以看到所有参数以及参数c的默认值
而经过装饰器的版本是什么样子呢?
天呐,所有信息都丢失了:wrapper闭包的签名变成了mult的签名!但是为什么呢?因为我们使用了functools.wraps!
事实是,functools.wraps仅仅保留所包装的可调用对象的:
1.名字
2.函数说明
这确实很好,但是却还不够(它也设置了__wrapped__属性,后面会详细介绍)。如果你现在觉得很蠢,请不要急。即便是我们中最聪明的人,也会跳进这个陷阱中。
这一情况最多算是一个烦恼,因为一个损坏的内省将会在你的Python解释器中破坏调试器内省或参数自动补全等功能:
然而,最差情况它还可能带来更严重的后果,例如如果你使用了一个采用了你的可调用对象的签名的框架或者库来做有用的事情2。
这个陷阱“咬”到我是在Pyramid里。Pyramid对不同数量的参数会采取不同的措施:如果你的视图获取了一个上下文对象,并且如果满足参数列表,则绑定到一个request对象中。当然,它会满足参数*args和**kw.但是,如果仅有一个参数,它可能不会绑定到你的视图中。所以你会得到一个关于一些上下文的你从没听过的含糊不清的TypeError,并且让你困惑不清。
并且,如果仅仅是因为我用了一个看起来无辜的装饰器导致了所有事情都以莫名其妙的方式崩了,我就不得不等待Cthulhu的回归了。
类方法
接下来,我们在一个类方法上试用我们的装饰器3。我的类方法用的最多的是在工厂类中(通常是异步的),所以我非常希望我的装饰器能同它们一起工作:
让我们调用C.cm()!
天4!
我将会停在这一点上,因为我感觉我已经传达我的观点足够多了。然而不幸的是,在你我甚至难以想象的角落,还存在数不胜数的这样的情况。
所以当你在编写一个软件给其他人用时,你应当警惕这一情况,并且考虑使用如下的救济方法。
解决方案
Python3.5以及INSPECT.SIGNATURE
这里便是上文提到的__wrapped__属性起作用的地方:在Python3.5中,inspect.signature和functools.wraps一起可以做正确的事,即返回“真正的”签名:
优点:
你不需要做任何事情;
缺点:
并没有修复类方法的问题;
Inspect.signature仅仅在Python3中存在,并且仅仅在Python3.5中做正确的事。(另一方面:这是在Python3.5之上唯一的未被弃用的获得签名的方法。)
这实际上有一点欺骗:签名仍然是坏的。inspect.signature仅仅是机智地骗了你。
总之,在除了Python3.5+之外的版本中,你的包就是坏的。这也成为了公共包的一个很差的选择。
BOLTONS
如今,我在几乎所有的项目中都开始使用MahmoudHashemi的boltons包了。它的funcutils模块包含了wraps的一个实现。它可以保留你的可调用对象的签名。
不论好坏,当定义一个类方法时,它的wraps就爆炸了
但是,它可以不用重写任何代码就能修复签名。仅仅从boltons.funcutils里使用wraps函数而不去使用functools中的wraps。
虽然boltons是一些实用工具的集合,你也可以单独使用其中的每一个子模块并且简单地把模块丢进你自己的工程中。
DECORATOR.PY
下一个简单的方法是单文件decorator.py包。它没有其他依赖包,并且作者们也鼓励如果其他人也想避免依赖包,就直接把它丢进自己的工程中。
它的一个缺点是你需要重写你的装饰器。不过我很喜欢它非闭包的风格。
和boltons一样,decorator.py对类方法不起作用,失败的方式也类似。
WRAPT
wrapt完成了上面所有的功能,并且做了更多。一般来说,它考虑到了所有你所未曾听过的边界情况。它同样也需要你重写你的装饰器:
同样,我也喜欢这样的风格。现在让我们用它来“兜兜风”:
看:
优点:
它们中最正确的一个;
做了更多相关的任务;
缺点:
尽管有一个可选的C扩展,但是却是它们中最慢的一个。正确性是以运行时间为代价的。
总结:
我在我自己的代码中使用wrapt因为我看重上述所有方法的正确性以及不要太大的“惊喜”。
如果你想要最少的(或是没有)依赖包,并且仅仅希望有一个能用的签名,次好的选择是或者使用boltons或者使用decorator.py,取决于你更喜欢哪种风格。
如果你决定不关心我所说的任何事情,你现在至少知道了当程序崩了的时候,你应该去找什么。
脚注:
同样有这篇文章没有讨论的类装饰器。一个类装饰器的漂亮的例子是attrs工程。
虽然我同意这不是一个最好的模式。
这实际上不是类方法本身而是关于装饰器。它是一个复杂的但是不常见的问题。类方法仅仅是它们最常见的,最有用的应用。
需要注意的是重新排序装饰器可以解决这个问题。
Python培训、Python培训班、Python培训机构,就选光环大数据!
还不够过瘾?想学习更多?点击 http://hadoop.aura.cn/python/ 进行Python学习!
大数据培训、人工智能培训、Python培训、大数据培训机构、大数据培训班、数据分析培训、大数据可视化培训,就选光环大数据!光环大数据,聘请专业的大数据领域知名讲师,确保教学的整体质量与教学水准。讲师团及时掌握时代潮流技术,将前沿技能融入教学中,确保学生所学知识顺应时代所需。通过深入浅出、通俗易懂的教学方式,指导学生更快的掌握技能知识,成就上万个高薪就业学子。 更多问题咨询,欢迎点击------>>>>在线客服!