python装饰器的本质,就是闭包!
我们一般谈Python的闭包,都是指普通的入参,而谈装饰器的时候,入参一定有函数!闭包和装饰器,返回的都是函数。函数是代码的最小封装单位,装饰器作用于函数,它不影响函数自身的执行,只是在函数的执行前后增加一些“装饰性”的动作。装饰器被称为python的语法糖(syntax sugar),也被视为python支持AOP编程(面向切面编程)的工具。
简单装饰器
以下代码实现了一个最简单的额装饰器,功能是给带上装饰器的函数,在函数执行前,增加一行print打印,代码如下:
test1函数加上了calling_trace装饰器,在第7行,这一行代码的作用,相同于第11行的注释。test1还是test1,这个名字没有变,只是换成了一个闭包函数的返回值,此闭包函数,就是装饰器,它接收一个函数作为参数。以上代码运行效果如下:
$ python3 deco.py
calling test1
test1 is runing...
我们还可以稍微复杂一点点,既然是装饰器,被装饰的函数执行前后都可以加点小动作。修改后的代码如下:
def calling_trace(func):
def wrapper():
print('calling', func.__name__)
a = func()
print('this is the reture:',a)
return wrapper
@calling_trace
def test2():
print('test2 is runing...')
return 'www.pynote.net'
# test2 = calling_trace(test2)
test2()
新的装饰器没有改名字,只是获取了被装饰函数的返回值,并将返回值打印出来。运行效果如下:
$ python3 deco.py
calling test2
test2 is runing...
this is the reture: www.pynote.net
test2函数执行前后,都增加了一些动作,作为装饰!
这就是装饰器的概念:不修改已有函数的代码,也不修改已有函数的调用处代码,却达到了丰富函数功能的效果!本质上装饰器是对函数的一种封装,只是我们要理解@语法。
闭包和装饰器
装饰器的本质就是闭包,我们如果把上面这段代码重写一遍,采用闭包的语法形式,不使用@语法,效果是完全一样的:
def calling_trace(func):
def wrapper():
print('calling', func.__name__)
a = func()
print('this is the reture:',a)
return wrapper
def test2():
print('test2 is runing...')
return 'www.pynote.net'
t = calling_trace(test2)
t()
以上代码,没有@语法的使用啦,用变量t来获取calling_trace返回的函数对象,然后调用,效果与使用装饰器完全一样:
$ python3 deco.py
calling test2
test2 is runing...
this is the reture: www.pynote.net
使用@语法,阅读代码就如吃糖!
装饰带参数的函数
如果被装饰的函数有参数,需要在装饰器内部原样复制函数的参数定义。请看示例:
def calling_trace(func):
def wrapper(a,b,c=3):
print('calling', func.__name__)
a = func(a,b,c)
print('reture value:',a)
return wrapper
@calling_trace
def test3(a,b,c=3):
print('test3 is runing...')
return a+b+c
test3(1,2,5)
test3(1,2)
装饰器返回的函数的参数定义,要与被装饰函数的参数定义保持一致!以上代码运行结果如下:
$ python3 deco.py
calling test3
test3 is runing...
reture value: 8
calling test3
test3 is runing...
reture value: 6
就算装饰参数个数不定的函数,语法上也是一样的,请看下面代码,test4函数的参数个数不定:
def calling_trace(func):
def wrapper(*args):
print('calling', func.__name__)
a = func(*args)
print('reture value:',a)
return wrapper
@calling_trace
def test4(*args):
print('test4 is runing...')
return sum(args)
test4(1,2,3,4,5,6,7,8)
test4(23,34,45,56)
*args表示一个tuple,在函数定义处出现,就是packing打包调用时的参数,在调用时出现,就是unpacking展开tuple。跟**kw(对应dict)用法一样。以上代码运行效果:
$ python3 deco.py
calling test4
test4 is runing...
reture value: 36
calling test4
test4 is runing...
reture value: 158
给带参数的函数加装饰器,还有一种更通用更常见的参数写法,这种写法在修改函数参数和调用处时,有可以反过来保持装饰器部分代码不变。示例如下:
def calling_trace(func):
def wrapper(*args, **kw):
print('calling', func.__name__)
a = func(*args, **kw)
print('reture value:',a)
return wrapper
@calling_trace
def test5(a,b,c=3):
print('test5 is runing...')
return a+b+c
test5(1,2)
test5(1,2,c=8)
装饰器中使用*args和**kw来接收和传递参数!这个地方要好好体会,这是python的一个特别精妙的地方。以上代码运行结果如下:
$ python3 deco.py
calling test5
test5 is runing...
reture value: 6
calling test5
test5 is runing...
reture value: 11
带参数的装饰器
本文以上示例,都是不带参数的装饰器,在使用@语法的时候,没有参数。而装饰器本身也可以带参数,通过在装饰器外再使用闭包,给装饰器封装其执行环境,可以使装饰器的功能更强大更灵活,也可以更好的控制函数的执行(比如在某些情况下不执行)。而带参数的装饰器,在语法上,也就跟闭包区分开来。(应该就是这个原因,闭包和装饰器在python中是分成了两个概念)
def calling_trace(run):
def deco(func):
def wrapper(*args, **kw):
print('calling', func.__name__)
if run == 1:
a = func(*args, **kw)
print('reture value:',a)
else:
print('not allow to run')
return wrapper
return deco
# test5 = calling_trace(run=1)(test5)
@calling_trace(run=1)
def test5(a,b,c=3):
print('test5 is runing...')
return a+b+c
@calling_trace(run=0)
def test6(*args):
print('test6 is runing...')
return sum(args)
test5(1,2)
test5(1,2,c=8)
test6(23,34,45,56)
先看一下这段代码的运行结果:
$ python3 deco.py
calling test5
test5 is runing...
reture value: 6
calling test5
test5 is runing...
reture value: 11
calling test6
not allow to run
达到预期,test5可以执行,而test6不允许执行。
calling_trace实际上就是一个闭包,它有一个参数,run,调用calling_trace(run=1)后,就相当于返回了一个有run这个参数的装饰器。然后再用这个装饰器去“修饰”它自己的参数func。这个效果,就如注释掉的那行代码。如果不使用@语法,把那行代码注释打开(放在test5的定义后面),运行效果一模一样!
多重装饰器
函数可以有多个装饰器!多个装饰器的效果,就相当于对函数进行了多层的封装包裹,而不同的装饰器对函数执行的功能影响,完全独立。比如有个装饰器用来控制函数是否能够被执行,另一个装饰器控制函数的所有raise出来的异常。
@a
@b
@c
def tt(): pass
# tt = a(b(c(tt)))
tt函数上面带3个装饰器,想过正如注释掉的那行代码。
我真的觉得python装饰器的设计,实在是非常精妙!
-- EOF --