解析Python中的装饰器
装饰器的作用一般可以概括为复用代码片段。
即不像 import
复用整个模块,也不像类的继承一样进行系统性的复用和演变,装饰器可以更加灵活的面向代码切片编程。
原理
如一开始所说,装饰器的本质其实就是闭包,或者说就是函数
1 | from functools import wraps |
装饰器中最为神秘的可能并不是装饰器本身,而是这个语法糖 @
,这个在装饰过程中起作用的语法(可以说是灵魂存在)。不过它的作用其实很简单,就是 执行函数
+ 赋值
。大概效果如下:
1 |
|
对于带参数的装饰器:
1 |
|
就这个语法糖 @
符号的使用要求其实很简单,即使不是一个装饰器,依然可以使用该符号,比如:
1 | def say(func): |
在装饰阶段,well, you are trapped
便会跃然纸上,如果进一步执行 well
,便会报错
1 | well() # 'NoneType' object is not callable |
well
之所以变成了 None
,其实是因为 say
默认返回值为 None
。如果换一种写法,可能更能明白现状:
1 | def say(func): |
此时 well
其实是 lambda: 1
。也就是说,其实装饰器的写法并不重要,重要的是返回一个 可执行对象
,甚至是说一个可以加一对 ()
然后运行的东西,所以还有可能出现下面神奇的东西
1 | def say(func): |
不知道这算不算python中 duck typing
如果知道了装饰过程是如何进行的,讲真就不用那么在意装饰器本身的书写模式了,而且也就可以更加灵活的使用语法糖了。不过说了装饰过程,装饰器本身其实还是有很多需要注意的地方:
1. 元信息丢失
由于装饰过程其实是一个赋值过程,那么最后得到的被装饰对象其实是装饰器返回出来的可执行对象。这一套狸猫换太子,一般会损失 __module__, __name__, __doc__
这些元信息,大多数情况下其实都不是很重要,在调试环节和纠错时会比较有用。functools
中的 wraps
装饰器恰好是用来恢复这些元信息的。不过需要注意的是,class
也可以被 wraps
恢复元信息,即:
1 |
|
语法和 A
的实例都不存在问题,问题在于 A
本身已经不再是 class
,而是一个方法,也就是说不管再怎么恢复,A类
已经不是 A类
了,而是 A 方法
。
2. 返回函数
如上所述,不管原来是什么,被装饰之后的对象变成了一个函数,即使装饰器和被装饰对象都是类也不行
1 | class Wrap(object): |
3. 执行阶段
在装饰器中,由于语法糖 @
会执行装饰器,所以第一层函数一定会被执行。可以说 参数修饰函数
(涉及到被装饰对象执行时上下文处理的函数,也就是 args_wrap
类似的函数) 才是在被装饰对象执行时真正执行的对象,在其外层的代码都会在装饰阶段就执行完毕。
这里的区别在于
- 装饰阶段在全局作用域执行,一般不会有竞争条件,而
参数修饰函数
却是在每次执行被装饰对象时都会运行,竞争条件与被装饰对象一致。也就是说如果在多线程环境下使用被装饰对象,那么可以通过参数装饰函数
控制并发条件 参数修饰函数
相对装饰器中的其他部分而言,使用的是内部作用域,不能对外部作用域直接进行赋值操作。这和闭包的使用是一致的
用途
装饰器的用途可以说是很广了。
从控制被装饰对象的 输入
、执行
和 输出
来分的话,有如下例子:
输入
比如通过装饰器传入额外参数:
1 | def mysql_context(connection_config=DEFAULT_MYSQL_CONFIG): |
以上是一个通过装饰器将额外的 cursor
传入被装饰函数的例子,并且能够进行自动上下文管理 (通过上下文管理器)。当然一般不推荐修改被装饰函数的函数签名
执行
干预被装饰对象的执行过程有很多种例子,记忆中比较多的用法包括:
- 单例模式的实例管理
- 权限验证 (根据权限信息选择执行被装饰函数还是执行无权限响应)
- 函数计时器 (记录函数运行时间)
例子比较多,其中单例模式的应用应该有另外的文章记录,其它应用应该比较直白
输出
这种用法比较少见,但是在少数需要进行消息分发的场景下,用装饰器可以很方便
1 | def dispatch(targets): |
以及一种对于上述三者都不怎么重视的应用
钩子注册
即对于被装饰对象的返回值不再重视,而是需要被程序其他部分知道被注册的对象即可
比如个人比较常用的一个例子就是路由注册
1 | _ROUTER = [] |
这里大概是在 tornado
中利用 router
函数进行路由的注册,代码思路应该在 这个项目 里有具体体现。这里对于类的返回值(实例)已经不再需要,所以直接在装饰器中舍弃了返回值
另一个知道的例子如 click ,一个终端命令行解析工具
总结
装饰器的出现,尤其是语法糖的使用,让python中的非侵入式开发变得十分方便,也让代码的复用级别进一步降低,也是 函数式编程
发展过程中出现的必然产物。
装饰器如果使用得当将会事半功倍,过度滥用也会导致头重脚轻。从整体项目来看,装饰器将python作为胶水语言的特点进一步突出,但是谁都知道,不可能整个项目都是胶水,在大型项目中,胶水更不应该出现太频繁,否则将会导致项目各模型之间穿孔严重,耦合加剧,甚者都会变成一锅浆糊。
重要的是,在合适的地方使用合适的工具
本文作者 : hellflame
原文链接 : https://hellflame.github.io/2019/10/05/decorators-in-python/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!