Python入门篇-装饰器
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.装饰器概述
装饰器(无参)
它是一个函数
函数作为它的形参
返回值也是一个函数
可以使用@functionname方式,简化调用
装饰器和高阶函数
装饰器是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
带参装饰器
它是一个函数
函数作为它的形参
返回值是一个不带参的装饰器函数
使用@functionname(参数列表)方式调用
可以看做在装饰器外层又加了一层函数
二.为什么要用装饰器
1>.在不是用装饰器的情况下,给某个函数添加功能
在解释为什么使用装饰器之前,完美来看一个需求:
一个加法函数,想增强它的功能,能够输出被调用过以及调用的参数信息
def add(x, y):
return x + y
增加信息输出功能:
def add(x, y):
print("call add, x + y") # 日志输出到控制台
return x + y
上面的加法函数是完成了需求,但是有以下的缺点
打印语句的耦合太高,换句话说,我们不推荐去修改初始的add函数原始代码。
加法函数属于业务功能,而输出信息的功能,属于非业务功能代码,不该放在业务函数加法中
2>.使用高阶函数给某个函数添加功能
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def add(x,y): 9 return x + y 10 11 def logger(func): 12 print('begin') # 增强的输出 13 f = func(4,5) 14 print('end') # 增强的功能 15 return f 16 17 print(logger(add)) 18 19 20 21 #以上代码输出结果如下: 22 begin 23 end 24 9
3>.解决了传参的问题,进一步改变
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def add(x,y): 9 return x + y 10 11 def logger(func,*args,**kwargs): 12 print('begin') # 增强的输出 13 f = func(*args,**kwargs) 14 print('end') # 增强的功能 15 return f 16 17 print(logger(add,5,y=60)) 18 19 20 21 #以上代码输出结果如下: 22 begin 23 end 24 65
4>.柯里化实现add函数功能增强
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 def add(x,y): 8 return x + y 9 10 def logger(fn): 11 def wrapper(*args,**kwargs): 12 print('begin') 13 x = fn(*args,**kwargs) 14 print('end') 15 return x 16 return wrapper 17 18 # print(logger(add)(5,y=50)) #海航代码等价于下面两行代码,只是换了一种写法而已 19 add = logger(add) 20 print(add(x=5, y=10)) 21 22 23 #以上代码输出结果如下: 24 begin 25 end 26 15
5>.装饰器语法糖
#!/usr/bin/env python #_*_coding:utf-8_*_ #@author :yinzhengjie #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ #EMAIL:y1053419035@qq.com """ 定义一个装饰器 """ def logger(fn): def wrapper(*args,**kwargs): print('begin') x = fn(*args,**kwargs) print('end') return x return wrapper @logger # 等价于add = logger(add),这就是装饰器语法 def add(x,y): return x + y print(add(45,40)) #以上代码输出结果如下: begin end 85
三.帮助文档之文档字符串
1>.定义python的文档字符串
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 """ 9 Python的文档 10 Python是文档字符串Documentation Strings 11 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号 12 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述 13 可以使用特殊属性__doc__访问这个文档 14 """ 15 16 def add(x,y): 17 """This is a function of addition""" 18 a = x+y 19 return x + y 20 21 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__)) 22 23 print(help(add)) 24 25 26 27 #以上代码执行结果如下: 28 name = add 29 doc = This is a function of addition 30 Help on function add in module __main__: 31 32 add(x, y) 33 This is a function of addition 34 35 None
2>.装饰器的副作用
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def logger(fn): 9 def wrapper(*args,**kwargs): 10 'I am wrapper' 11 print('begin') 12 x = fn(*args,**kwargs) 13 print('end') 14 return x 15 return wrapper 16 17 @logger #add = logger(add) 18 def add(x,y): 19 '''This is a function for add''' 20 return x + y 21 22 23 print("name = {}\ndoc= {}".format(add.__name__, add.__doc__)) #原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决? 24 25 26 27 28 #以上代码执行结果如下: 29 name = wrapper 30 doc= I am wrapper
3>.提供一个函数,被封装函数属性==copy==> 包装函数属性
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 """ 9 通过copy_properties函数将被包装函数的属性覆盖掉包装函数 10 凡是被装饰的函数都需要复制这些属性,这个函数很通用 11 可以将复制属性的函数构建成装饰器函数,带参装饰器 12 """ 13 def copy_properties(src, dst): # 可以改造成装饰器 14 dst.__name__ = src.__name__ 15 dst.__doc__ = src.__doc__ 16 17 def logger(fn): 18 def wrapper(*args,**kwargs): 19 'I am wrapper' 20 print('begin') 21 x = fn(*args,**kwargs) 22 print('end') 23 return x 24 copy_properties(fn, wrapper) 25 return wrapper 26 27 @logger #add = logger(add) 28 def add(x,y): 29 '''This is a function for add''' 30 return x + y 31 32 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__)) 33 34 35 36 37 #以上代码执行结果如下: 38 name = add 39 doc = This is a function for add
4>.提供一个函数,被封装函数属性==copy==> 包装函数属性,改造成带参装饰器
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 8 def copy_properties(src): # 柯里化 9 def _copy(dst): 10 dst.__name__ = src.__name__ 11 dst.__doc__ = src.__doc__ 12 return dst 13 return _copy 14 15 def logger(fn): 16 @copy_properties(fn) # wrapper = copy_properties(fn)(wrapper) 17 def wrapper(*args,**kwargs): 18 'I am wrapper' 19 print('begin') 20 x = fn(*args,**kwargs) 21 print('end') 22 return x 23 return wrapper 24 25 @logger #add = logger(add) 26 def add(x,y): 27 '''This is a function for add''' 28 return x + y 29 30 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__)) 31 32 33 34 #以上代码执行结果如下: 35 name = add 36 doc = This is a function for add
四.装饰器案例
1>.无参装饰器
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 import datetime 8 import time 9 10 """ 11 定义一个装饰器 12 """ 13 def logger(fn): 14 def wrap(*args, **kwargs): 15 # before 功能增强 16 print("args={}, kwargs={}".format(args,kwargs)) 17 start = datetime.datetime.now() 18 ret = fn(*args, **kwargs) 19 # after 功能增强 20 duration = datetime.datetime.now() - start 21 print("function {} took {}s.".format(fn.__name__, duration.total_seconds())) 22 return ret 23 return wrap 24 25 @logger # 相当于add = logger(add),调用装饰器 26 def add(x, y): 27 print("===call add===========") 28 time.sleep(2) 29 return x + y 30 31 print(add(4, y=7)) 32 33 34 35 #以上代码输出结果如下: 36 args=(4,), kwargs={'y': 7} 37 ===call add=========== 38 function add took 2.000114s. 39 11
2>.有参装饰器
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 import datetime,time 8 9 def copy_properties(src): # 柯里化 10 def _copy(dst): 11 dst.__name__ = src.__name__ 12 dst.__doc__ = src.__doc__ 13 return dst 14 return _copy 15 16 """ 17 定义装饰器: 18 获取函数的执行时长,对时长超过阈值的函数记录一下 19 """ 20 def logger(duration): 21 def _logger(fn): 22 @copy_properties(fn) # wrapper = wrapper(fn)(wrapper) 23 def wrapper(*args,**kwargs): 24 start = datetime.datetime.now() 25 ret = fn(*args,**kwargs) 26 delta = (datetime.datetime.now() - start).total_seconds() 27 print('so slow') if delta > duration else print('so fast') 28 return ret 29 return wrapper 30 return _logger 31 32 @logger(5) # add = logger(5)(add) 33 def add(x,y): 34 time.sleep(3) 35 return x + y 36 37 print(add(5, 6)) 38 39 40 41 #以上代码执行结果如下: 42 so fast 43 11
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 import datetime,time 8 9 def copy_properties(src): # 柯里化 10 def _copy(dst): 11 dst.__name__ = src.__name__ 12 dst.__doc__ = src.__doc__ 13 return dst 14 return _copy 15 16 """ 17 定义装饰器: 18 获取函数的执行时长,对时长超过阈值的函数记录一下 19 """ 20 def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): 21 def _logger(fn): 22 @copy_properties(fn) # wrapper = wrapper(fn)(wrapper) 23 def wrapper(*args,**kwargs): 24 start = datetime.datetime.now() 25 ret = fn(*args,**kwargs) 26 delta = (datetime.datetime.now() - start).total_seconds() 27 if delta > duration: 28 func(fn.__name__, duration) 29 return ret 30 return wrapper 31 return _logger 32 33 @logger(5) # add = logger(5)(add) 34 def add(x,y): 35 time.sleep(3) 36 return x + y 37 38 print(add(5, 6)) 39 40 41 42 #以上代码输出结果如下: 43 11
五.functools模块
1>.functools概述
functools.update_wrapper(wrapper, wrapped, assigned=WRAPPER_ASSIGNMENTS,updated=WRAPPER_UPDATES) 类似copy_properties功能 wrapper 包装函数、被更新者,wrapped 被包装函数、数据源 元组WRAPPER_ASSIGNMENTS中是要被覆盖的属性'__module__', '__name__', '__qualname__', '__doc__', '__annotations__'模块名、名称、限定名、文档、参数注解 元组WRAPPER_UPDATES中是要被更新的属性,__dict__属性字典 增加一个__wrapped__属性,保留着wrapped函数
2>.functools模块案例
1 #!/usr/bin/env python 2 #_*_coding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie/tag/python%E8%87%AA%E5%8A%A8%E5%8C%96%E8%BF%90%E7%BB%B4%E4%B9%8B%E8%B7%AF/ 5 #EMAIL:y1053419035@qq.com 6 7 import datetime, time, functools 8 9 def logger(duration, func=lambda name, duration: print('{} took {}s'.format(name, duration))): 10 def _logger(fn): 11 @functools.wraps(fn) 12 def wrapper(*args,**kwargs): 13 start = datetime.datetime.now() 14 ret = fn(*args,**kwargs) 15 delta = (datetime.datetime.now() - start).total_seconds() 16 if delta > duration: 17 func(fn.__name__, duration) 18 return ret 19 return wrapper 20 return _logger 21 22 @logger(5) # add = logger(5)(add) 23 def add(x,y): 24 time.sleep(1) 25 return x + y 26 27 print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n') 28 29 30 31 32 #以上代码执行结果如下: 33 11 34 add 35 <function add at 0x0000000002A0F378> 36 {'__wrapped__': <function add at 0x0000000002A0F378>}