一、什么是装饰器
装饰器可以让其他函数在不需要做任何代码改变的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
在 Python 中,函数是第一类对象,也就是说,函数可以做为参数传递给另外一个函数,一个函数可以将另一函数作为返回值,这就是装饰器实现的基础。
装饰器本质上是一个函数,它接受一个函数作为参数。
装饰器的应用场景:插入日志、性能测试、事务处理、缓存等场景。
二、装饰器的形成过程
2.1、不使用装饰器(使用闭包函数)
如果想要测试一个函数的执行时间,在不改变这个函数的前提下可以这样做
import time def func1(): print('aaa') def timer(func): def inner(): start_time = time.time() func() end_time = time.time() print('程序用时%s'%(end_time - start_time)) return inner func1 = timer(func1) func1()
结果:
aaa
程序用时0.0
2.2、不带参数的装饰器
如果有多个函数都需要测试运行时间,就需要分别定义多个func2、func3等等,再调用这样函数,非常麻烦
所以python提供了语法糖,也叫装饰器。
import time def timer(func): def inner(): start_time = time.time() func() end_time = time.time() print('程序用时%s'%(end_time - start_time)) return inner @timer #这里相当于执行了func1 = timer(func1) def func1(): print('aaa') func1()
结果:
aaa
程序用时0.0
func1其实就是执行了timer(func1),返回值其实就是inner,
那么执行func1()其实就是执行了inner(),而inner()函数其实又是执行了传入的函数func1()并打印了一句话
2.3、带参数的装饰器
如果函数需要传入一个参数,那么上面的程序会报错,因为在执行timer()函数时,内部其实是调用了func函数,但是这里并没有给func()函数传入参数,所以会报错。那么尝试给timer()函数也传入一个参数。
import time def timer(func): def inner(a): #inner()需要传入参数给里面的子函数func() start_time = time.time() func(a) #调用传入的带参数的函数func1() end_time = time.time() print('程序用时%s'%(end_time - start_time)) return inner @timer #这里相当于执行了func1 = timer(func1) def func1(n): print(n) func1(100)
结果:
100
程序用时0.0
流程:
1)func1其实就是在执行timer(func1),也就是得到了返回值inner
2)func1(100)其实是在执行inner(100)
3)inner(100)其实是在执行func(100)和print语句
2.4、可以接收所有类型的参数的装饰器
上面的例子中只是传入了一个参数,如果传入2个或多个参数,会报错如下:
TypeError: inner() takes 1 positional argument but 2 were given
那么装饰器也需要随着传入多个参数,这显然是不方便的。
那么就需要在装饰器的形参中使用万能参数(*args, **kwargs)来接收所有类型的实参。
import time def timer(func): def inner(*args, **kwargs): #这里要求使用万能参数来接收所有类型的多个参数 start_time = time.time() func(*args, **kwargs) #这里也是要使用万能参数 end_time = time.time() print('程序用时%s'%(end_time - start_time)) return inner @timer #这里相当于执行了func1 = timer(func1) def func1(x,y): print(x,y) func1(100,200)
结果:
100 200
程序用时0.0
2.5、有返回值的参数器
如果被装饰函数有return返回值,那么装饰器内部也需要将被装饰函数的值返回
def timer(func): def inner(*args, **kwargs): 执行代码前要做的 ret = func(*args, **kwargs) 执行代码后要做的 return ret return inner
例子:
import time def timer(func): def inner(*args, **kwargs): #这里要求使用万能参数来接收所有类型的多个参数 start_time = time.time() ret = func(*args, **kwargs) #这里也是要使用万能参数 end_time = time.time() print('程序用时%s'%(end_time - start_time)) return ret return inner @timer def func1(x,y): return x+y print(func1(100,200))
结果:
程序用时0.0
300
2.6、装饰器查看函数信息
如果被装饰的函数中存在函数说明文档时
def func1(x,y): ''' 这是func1的说明文档 ''' print(x,y) #func1(100,200) print(func1.__doc__) #查看函数注释的方法 print(func1.__name__) #查看函数名的方法
结果:
这是func1的说明文档
func1
但是当函数加上装饰器后则不能正常显示被装饰函数的注释和函数名,而是显示了装饰器的注释和方法
None
inner
解决方法:使用warps
import time from functools import wraps def timer(func): @wraps(func) #加在最内层函数的正上方 def inner(*args, **kwargs): start_time = time.time() ret = func(*args, **kwargs) end_time = time.time() print('程序用时%s'%(end_time - start_time)) return ret return inner @timer #这里相当于执行了func1 = timer(func1) def func1(x,y): ''' 这是func1的说明文档 ''' print(x,y) #func1(100,200) print(func1.__doc__) print(func1.__name__)
结果:
这是func1的说明文档
func1
三、 开放封闭原则
对扩展是开放的
对于任何一个程序,在设计之初是可能将所有的功能都一次性设计好,后续一般都会有功能的添加、修改等操作,所以就必须要求代码是可
扩展的。
对修改是封闭的
在一个函数被开发出来之后很有可能会被其他程序引用,这个如果修改了这个函数的代码,就很有可能会影响到其他函数或程序。
而装饰器完美的遵循了这个开放封闭原则
四、装饰器的功能和固定结构
4.1、不支持函数说明文档的
def timer(func): def inner(*args, **kwargs): 执行代码前要做的 ret = func(*args, **kwargs) 执行代码后要做的 return ret return inner
4.2、支持函数说明文档的
from functools import wraps def timer(func): @wraps(func) #加在最内层函数的正上方 def inner(*args, **kwargs): 执行代码前要做的 ret = func(*args, **kwargs) 执行代码后要做的 return ret #return要放在最后,因为return后函数也就退出了 return inner
五、带参数的装饰器
如果有大量的函数使用了同一个装饰器,这时需要将这些函数同时取消掉这个装饰器,那么一个一个取消就太麻烦了。
这时可以在设计装饰器的时候就让装饰器带一个参数,当需要生效或者取消时可以改变被装饰函数上面的装饰器参数。
import time from functools import wraps #修改装饰器代码,在原装饰器外面再包裹一层函数用来接收flag状态 def outer(flag): def timer(func): @wraps(func) def inner(*args, **kwargs): if flag: #这里判断flag的bool值,如果为True则执行下面代码,如果为False则不执行,从而使装饰器失效 start_time = time.time() ret = func(*args, **kwargs) end_time = time.time() print('程序用时%s'%(end_time - start_time)) return ret return inner return timer #这里加上一句,返回原装饰器函数 @outer(False) #这里装饰器改为新的装饰器并传入参数True或False,来控制装饰器的启用状态 def func1(x,y): print(x,y) func1(100,200)
结果:当新的装饰器outer()参数为False时则装饰器失效,如果想再次启用该装饰器,只需要批量将outer()参数改为True即可
六、多个装饰器装饰一个函数
当某个函数需要被多个装饰器装饰时,只需要在该函数上面添加多个装饰器即可
def wrapper1(func): def inner(*args, **kwargs): 执行代码前要做的 ret = func(*args, **kwargs) 执行代码后要做的 return ret return inner def wrapper2(func): def inner(*args, **kwargs): 执行代码前要做的 ret = func(*args, **kwargs) 执行代码后要做的 return ret return inner @wrapper2 @wrapper1 def func(): print('111') func()
这里func()函数的状态是先被wrapper1装饰,再被wrapper2装饰。
例子:
def wrapper1(func): def inner(*args, **kwargs): print('wrapper1,before func') ret = func(*args ,**kwargs) print('wrapper1,after func') return ret return inner def wrapper2(func): def inner(*args, **kwargs): print('wrapper2,before func') ret = func(*args, **kwargs) print('wrapper2,after func') return ret return inner @wrapper2 #f = wrapper2(f),里面的f是inner1,外面的f是inner2 @wrapper1 #f = wrapper2(f),里面的f是函数名f,外面的f是inner1 def f(): print('haha')
结果:
wrapper2,before func
wrapper1,before func
haha
wrapper1,after func
wrapper2,after func
七、例子
例子1、记录函数的调用日志:
from datetime import datetime def log(func): def decorator(*args, **kwargs): print('Function ' + func.__name__ + ' has been called at ' + datetime.now().strftime('%Y-%m-%d %H:%M:%S')) ret = func(*args, **kwargs) return ret return decorator
@log def add(x, y): return x + y print(add(1, 2))
结果:
Function add has been called at 2018-11-01 18:01:34
3