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 + y10 11 def logger(func):12 print('begin') # 增强的输出13 f = func(4,5)14 print('end') # 增强的功能15 return f16 17 print(logger(add))18 19 20 21 #以上代码输出结果如下:22 begin23 end24 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 + y10 11 def logger(func,*args,**kwargs):12 print('begin') # 增强的输出13 f = func(*args,**kwargs)14 print('end') # 增强的功能15 return f16 17 print(logger(add,5,y=60))18 19 20 21 #以上代码输出结果如下:22 begin23 end24 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 x16 return wrapper17 18 # print(logger(add)(5,y=50)) #海航代码等价于下面两行代码,只是换了一种写法而已19 add = logger(add)20 print(add(x=5, y=10))21 22 23 #以上代码输出结果如下:24 begin25 end26 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 + yprint(add(45,40))#以上代码输出结果如下:beginend85
三.帮助文档之文档字符串
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 Strings11 在函数语句块的第一行,且习惯是多行的文本,所以多使用三引号12 惯例是首字母大写,第一行写概述,空一行,第三行写详细描述13 可以使用特殊属性__doc__访问这个文档14 """15 16 def add(x,y):17 """This is a function of addition"""18 a = x+y19 return x + y20 21 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__))22 23 print(help(add))24 25 26 27 #以上代码执行结果如下:28 name = add29 doc = This is a function of addition30 Help on function add in module __main__:31 32 add(x, y)33 This is a function of addition34 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 x15 return wrapper16 17 @logger #add = logger(add)18 def add(x,y):19 '''This is a function for add'''20 return x + y21 22 23 print("name = {}\ndoc= {}".format(add.__name__, add.__doc__)) #原函数对象的属性都被替换了,而使用装饰器,我们的需求是查看被封装函数的属性,如何解决?24 25 26 27 28 #以上代码执行结果如下:29 name = wrapper30 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 x24 copy_properties(fn, wrapper)25 return wrapper26 27 @logger #add = logger(add)28 def add(x,y):29 '''This is a function for add'''30 return x + y31 32 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__))33 34 35 36 37 #以上代码执行结果如下:38 name = add39 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 dst13 return _copy14 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 x23 return wrapper24 25 @logger #add = logger(add)26 def add(x,y):27 '''This is a function for add'''28 return x + y29 30 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__))31 32 33 34 #以上代码执行结果如下:35 name = add36 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() - start21 print("function {} took {}s.".format(fn.__name__, duration.total_seconds()))22 return ret23 return wrap24 25 @logger # 相当于add = logger(add),调用装饰器26 def add(x, y):27 print("===call add===========")28 time.sleep(2)29 return x + y30 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 dst14 return _copy15 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 ret29 return wrapper30 return _logger31 32 @logger(5) # add = logger(5)(add)33 def add(x,y):34 time.sleep(3)35 return x + y36 37 print(add(5, 6))38 39 40 41 #以上代码执行结果如下:42 so fast43 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 dst14 return _copy15 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 ret30 return wrapper31 return _logger32 33 @logger(5) # add = logger(5)(add)34 def add(x,y):35 time.sleep(3)36 return x + y37 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 ret19 return wrapper20 return _logger21 22 @logger(5) # add = logger(5)(add)23 def add(x,y):24 time.sleep(1)25 return x + y26 27 print(add(5, 6), add.__name__, add.__wrapped__, add.__dict__, sep='\n')28 29 30 31 32 #以上代码执行结果如下:33 1134 add3536 { '__wrapped__': }