enter image description here

平时用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: {}
document_it 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: {}
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闭包。整个过程用以下图示来说明就一目了然了:

enter image description here

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

现在反过来调用:

>>> @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

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

enter image description here

三. 小结

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

我的知乎专栏