一、什么是装饰器

装饰器可以让其他函数在不需要做任何代码改变的前提下,增加额外的功能,装饰器的返回值也是一个函数对象。
在 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('')
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

最新文章

  1. 解读发布:.NET Core RC2 and .NET Core SDK Preview 1
  2. Android自定义折线图
  3. Robot Framework安装教程
  4. BizTalk 2013 Beta 新特性介绍
  5. oracle物化视图
  6. poj 1469(二分图 最大匹配)
  7. Linux下查看内核、CPU、内存及各组件版本的命令和方法
  8. OCP-1Z0-051-名称解析-文章32称号
  9. 类string解析
  10. ng机器学习视频笔记(一)——线性回归、代价函数、梯度下降基础
  11. ubuntu18.04安装搜狗拼音
  12. OpenCV函数学习:cvRound,cvFloor,cvCeil
  13. [Swift]LeetCode553. 最优除法 | Optimal Division
  14. background-attachment属性
  15. Window快捷键
  16. Android-Xml文件生成,Xml数据格式写入
  17. Android开发问题积累 <加载在线Gif><WebView无法加载网页图片>
  18. Android 增量更新研究
  19. 「Python」Numpy equivalent of MATLAB's cell array
  20. 【转】cpu的核心数与线程数的关系

热门文章

  1. 微信小程序客服消息使用
  2. Java NIO系列教程(五)Buffer
  3. PHP中的static的理解
  4. 简单的一个MySQL类的实现:
  5. datetime模块常用功能小结
  6. js时间加减
  7. Linux网卡配置文件参数注释
  8. Chapter1:Qt概念
  9. 小朋友学C语言(6)
  10. SQL Server 查询性能优化——创建索引原则(一)