转载

瓜的程序世界 : Python装饰器

瓜的程序世界 : Python装饰器

平时用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 函数,该函数一方面有其自己的逻辑,另一方面也撩了撩 funcresult = 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: {} Result: 8 8

这里不难理解:

  • cooler_add_ints = document_it(add_ints) 中, document_it(add_ints) 调用返回了一个持有 add_ints 的闭包函数 new_functioncooler_add_ints 引用;
  • cooler_add_ints(3, 5) 调用时,参数 35 传给了 new_function 函数(实际上 cooler_add_ints(3, 5) 可以看成 new_function(3, 5) )。然后, new_function 再在自己内部用参数 35 调用第一步传进的 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: {} 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: new_function Positional arguments: (3, 5) Keyword arguments: {} Result: 64 64

来分析一下。首先应用的是 @square_it ,此时返回的闭包其实是 another_new_function 。之后才是将 another_new_function 应用给 @document_it ,返回 new_function 闭包。整个过程用以下图示来说明就一目了然了:

瓜的程序世界 : Python装饰器

反过来的第二种调用情况:

现在反过来调用:

>>> @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: {} Result: 8 64

理解起来的思路跟上一种情况一样:

瓜的程序世界 : Python装饰器

三. 小结

仔细想想,装饰器还是蛮有意义的,只要想在方法执行前后添加一些行为,都有可能用到它,比如拦截器、路由设置、调试增强等等任务。希望后续自己也多留意一些使用场景。

我的博客

我的知乎专栏

原文  http://www.ituring.com.cn/article/261148
正文到此结束
Loading...