Python 3----装饰器
转自:https://zhuanlan.zhihu.com/p/21696291
平时用Python的方式都是短平快,很少用到装饰器特性,而Python装饰器的实现也是有一定“套路”的,恰好最近在看「Python语言及其应用」这本书,看到里面的装饰器章节(4.9 装饰器的内容),觉得有必要进一步描述,以强化一下概念。
一. 预备知识
首先你得理解4.7.9 闭包的概念。举该章节的例子:
def knights2(saying): def inner2(): return "We are the knights who say: '%s'" % saying return inner2
这里的关键是return inner2,其返回一个函数inner2(不是该函数执行结果,下面提到的inner2都是指return inner2中的inner2),原书说inner2是闭包。但更确切地讲,由于inner2引用了外层变量saying,闭包应该是inner2加上绑定了saying的作用域。这样,return inner2之后,saying才不会消失。当然,闭包的具体表现是通过inner2体现出来的:
a = knights2('Duck') b = knights2('Hasenpfeffer') print(a(), '---', a) print(b(), '---', b)
输出:
We are the knights who say: 'Duck' --- <function knights2.<locals>.inner2 at 0x0000026479031510> We are the knights who say: 'Hasenpfeffer' --- <function knights2.<locals>.inner2 at 0x0000026479031598>
二. 装饰器
装饰器的定义是:装饰器实质上是一个函数。它把一个函数作为输入并且返回另外一个函数。其实其是闭包概念的深化。依然是本书例子:
def document_it(func): def new_function(*args, **kwargs): print('Running function:', func.__name__) print('Positional arguments:', args) print('Keyword arguments:', kwargs) result = func(*args, **kwargs) print('document_it Result:', result) return result return new_function
这里定义了一个装饰器,往函数document_it传入一个函数func,然后在document_it里定义一个new_function函数,该函数一方面有其自己的逻辑,另一方面也撩了撩func:result = func(*args, **kwargs),请记住,这里是确实调用了func。最后,将整个new_function返回给外部了,这个new_function从模糊概念上来讲,是个闭包,如“预备知识”里讲的,它不是该函数执行结果。
接下来,定义即将要应用装饰器函数的函数add_ints:
def add_ints(a, b): return a + b
手工应用装饰器函数方式
先来看看如何手工应用装饰器函数:
>>> cooler_add_ints = document_it(add_ints) >>> cooler_add_ints(3, 5) Running function: add_ints Postitional arguments:(3, 5) Keyword arguments: {} document_it Result: 8 8
这里不难理解:
- cooler_add_ints = document_it(add_ints)中,document_it(add_ints)调用返回了一个持有add_ints的闭包函数new_function给cooler_add_ints引用;
- cooler_add_ints(3, 5)调用时,参数3跟5传给了new_function函数(实际上cooler_add_ints(3, 5)可以看成new_function(3, 5))。然后,new_function再在自己内部用参数3跟5调用第一步传进的func——即add_ints,生成结果。
理解以上步骤很重要,因为下面阐述的内容,其机制是一致的。
使用装饰器名字来装饰函数
相对于前面的手工应用装饰器函数方式,Python提供的装饰器名字方式,则让Python代码显得更为高大上一些:
@document_it def add_ints(a, b): return a + b
运行结果如下:
>>> add_ints(3, 5) Start function add_ints Positional arguments: (3, 5) Keyword arguments: {} document_it Result: 8 8
这里的机理跟前面的手工应用装饰器函数方式一样,就语法糖而已,在此不再多说。
应用多个装饰器
可以为函数应用多个装饰器,如本书提及:“靠近装饰目标函数定义(def上面)的装饰器最先执行,然后依次执行上面的”。这里的执行,还请理解成“生成闭包的过程”。
再定义一个本书中新的装饰器函数:
def square_it(func): def another_new_function(*args, **kwargs): result = func(*args, **kwargs) return result * result return another_new_function
其实,理解了“手工应用装饰器函数方式”中的两个步骤,再理解这里的内容就不是什么难事了,无非就是多套了一层膜。
先来看看第一种情况:
>>> @document_it ... @square_it ... def add_ints(a, b): ... return a + b ... >>> add_ints(3, 5) Running function: another_new_function Positional arguments: (3, 5) Keyword arguments: {} document_it Result: 64 64
来分析一下。首先应用的是@square_it,此时返回的闭包其实是another_new_function。之后才是将another_new_function应用给@document_it,返回new_function闭包。整个过程用以下图示来说明就一目了然了:
现在反过来调用:
>>> @square_it ... @document_it ... def add_ints(a, b): ... return a + b ... >>> add_ints(3, 5) Running function: add_ints Positional arguments: (3, 5) Keyword arguments: {} document_it Result: 8 64
理解起来的思路跟上一种情况一样:
三. 小结
仔细想想,装饰器还是蛮有意义的,只要想在方法执行前后添加一些行为,都有可能用到它,比如拦截器、路由设置、调试增强等等任务。希望后续自己也多留意一些使用场景。