一、什么事装饰器:
装饰器的本质是一个函数,在不改动其他函数的前提下,对函数的功能进行拓展,举一个知乎上引用的例子,冬天天冷了不能只穿短裤了,要在短裤之外套上长裤,但是长裤并没有影响短裤遮羞的最根本作用。个人觉得有点类似java里面的aop
二、装饰器的常用场景:
- 引入日志
- 统计函数执行时间
- 执行函数前预处理
- 执行函数后的清理功能
- 权限校验
- 制作缓存
- 使用装饰器扩展类的功能
三、装饰器的类型
3.1.最简单的装饰器:
def decorate(func):
print('正在装饰')
def decorate_inner():
print('正在验证')
func()
return decorate_inner
@decorate
def func_test():
print('func_test is running')
func_test()
分析一下上述被修饰函数func_test的执行流程:
- 执行func_test()的时候,发现func_test头顶有装饰器decorate,所以会率先执行decorate。@decorate等价于func_test = decorate(func_test);
- 首先执行decorate(func_test),以函数func_test作为参数,在调用decorate的时候,首先会执行print('正在装饰')语句,然后返回其内函数decorate_inner。
- 从而函数func_test 接受了 装饰器的 内函数 decorate_inner,即func_test=decorate_inner。当开始调用func_test()的时候,即相当于调用了decorate_inner(),从而执行decorate_inner的调用,执行print('正在验证'),再执行func_test本体。
3.2.多个装饰器:
def decorate1(func):
print('正在装饰————1')
def decorate_inner():
print('正在验证————1')
func()
return decorate_inner
def decorate2(func):
print('正在装饰————2')
def decorate_inner():
print('正在验证————2')
func()
return decorate_inner
@decorate1
@decorate2
def func_test():
print('func_test is running')
func_test()
分析一下上述被修饰函数func_test的执行流程:
- 当执行函数func_test()的时候,发现该函数具有两个装饰器,装饰器执行的顺序从下至上。
- 先执行第2个装饰器decorate2,把func_test作为参数传给decorate2,相当于func_test=decorate2(func_test)
- 然后在执行第1个装饰器decorate1,把decorate2的返回值作为参数传给decorate1,相当于func_test=decorate1(decorate2(func_test))
- 所以当执行func_test的时候控制台的展示应该是:
正在装饰————2
正在装饰————1
正在验证————1
正在验证————2
func_test is running - 当装饰器更多的时候,依此顺序累加。
3.3.有参数函数的装饰器
如果被修饰函数带有参数是,继续使用3.1的装饰器则会报错,需要将装饰器的写法进行如下修改
def decorate(func):
print('正在装饰')
def decorate_inner(*args, **kwargs):
print('正在验证')
func(*args, **kwargs)
return decorate_inner
@decorate
def func_test(param):
print('func_test is running',str(param))
func_test('sth param')
分析使用3.1那种方法会报错的原因、上述被修饰函数func_test的执行流程:
- 当func_test被decorate修饰的时候,func_test的实际指向就变成了decorate函数的inner函数,但是3.1的inner函数并没有接受参数,所以用3.1的装饰器去修饰有参数的函数时就会抛出异常。
- 本文所介绍的装饰函数 的 inner 函数在接受参数的时候使用了,*args 和 **kwargs 这样做的好处是,当被修饰函数的参数未知的情况下,修饰函数可以对参数进行动态的匹配
3.4.修饰有返回值的函数
如果修饰函数存在返回值,那么如果继续使用上述3.1到3.3的装饰器,则会使print 出的值总是None
def decorate(func):
print('正在装饰')
def decorate_inner(*args,**kwargs):
print('正在验证')
return func(*args,**kwargs)
return decorate_inner
@decorate
def func_test():
print('func_test is running')
return 'return sth'
print func_test()
分析原因及上述被修饰函数func_test的执行流程:
- 因为func_test被decorate修饰,所以func_test的指引变成了decorate的内函数decorate_inner,但是3.1到3.3的decorate_inner都不具备返回值,所以print出来的值永远是None
- 所以,这个时候就要将decorate_inner中的func()作为返回值return出来。
3.5.带有参数的装饰器
def decorate(*args, **kwargs):
print 'decorate is running', args, kwargs
def decorate_outer(func):
print 'decorate_outer is running'
def decorate_inner(*args, **kwargs):
print 'decorate_inner is running'
return func(*args, **kwargs)
return decorate_inner
return decorate_outer
@decorate('deco')
def func_test():
print 'func_test is running'
return 'sth return'
print func_test()
上述函数的运行结果如下:
decorate is running ('deco',) {}
decorate_outer is running
decorate_inner is running
func_test is running
sth return
相比于无参数装饰器,带参数的装饰器只是用来‘加强装饰’的,如果希望装饰器可以根据参数的不同,对不同的函数进行不同的装饰,那么带参装饰器是一个很好的选择。
函数运行结果社上述大同小异,此后不做多余解释。
3.6.类装饰器
class Decorate():
def __init__(self, func):
self._func = func
def __call__(self):
print 'class is decorating'
self._func()
print 'decorate is ending'
@Decorate
def func_test():
print 'func_test is running'
func_test()
四、拓展——关于wraps和拓展类的功能
4.1.wraps
使用装饰器虽然使代码得以复用,但是却覆盖了原函数的一些信息,比如__name__等。
具体情况如下:
def decorate(func):
print('正在装饰')
def decorate_inner():
print('正在验证')
func()
return decorate_inner
@decorate
def func_test():
print('func_test is running')
print func_test.__name__
这个时候得到的值为:
decorate_inner
但是我们的函数名称为func_test,所以这个时候需要引入Python基本库中的functools。
具体实现如下:
from functools import wraps
def decorate(func):
print('正在装饰')
@wraps(func)
def decorate_inner():
print('正在验证')
func()
return decorate_inner
@decorate
def func_test():
print('func_test is running')
print func_test.__name__
这样控制台打印出的就是我们预期的效果
func_test
4.2.使用装饰器拓展类的功能
因为篇幅已经有点长了,本文就不详细介绍了,感兴趣的童鞋可以挪步下面的link
PS:本文参考了《Python快速编程入门》一书和知乎《如何理解Python装饰器》的部分内容