Python入门篇-装饰器
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
5>.使用Python提供的wrap装饰器修改被装饰的doc信息
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 from functools import wraps 7 8 9 def logger(fn): 10 11 @wraps(fn) #其实查看wraps源码是利用update_wrapper实现的(需要有偏函数知识),但是实际开发中我们推荐使用wraps装饰去。 12 def wrapper(*args,**kwargs): 13 '''This is a function for wrapper''' 14 ret = fn(*args,**kwargs) 15 return ret 16 return wrapper 17 18 19 @logger 20 def add(x,y): 21 '''This is a function for add''' 22 return x + y 23 24 print("name = {}\ndoc = {}".format(add.__name__, add.__doc__)) 25 26 27 28 29 30 #以上代码执行结果如下: 31 name = add 32 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>}
六.小试牛刀
1>.实现Base64编码(要求自己实现算法,不用库)

1 #!/usr/bin/env python#_*_conding:utf-8_*_ 2 #@author :yinzhengjie 3 #blog:http://www.cnblogs.com/yinzhengjie 4 5 from string import ascii_lowercase, ascii_uppercase, digits 6 import base64 7 8 9 bytesBase64 = (ascii_uppercase + ascii_lowercase + digits + "+/").encode("utf-8") #注意,我们这里拿到的是一个字节序列哟~ 10 11 12 def base64encode(src:str,code_type="utf-8") -> bytes: 13 res = bytearray() 14 15 if isinstance(src,str): 16 _src = src.encode(code_type) 17 elif isinstance(src,bytes): 18 _src = src 19 else: 20 raise TypeError 21 22 length = len(_src) 23 24 for offset in range(0,length,3): 25 triple = _src[offset:offset+3] #切片可以越界 26 27 r = 3 - len(triple) 28 29 if r: 30 triple += b'\x00' * r #便于计算先补零,即传入的字符串转换成字节后不足3个字节就补零 31 32 """ 33 bytes和bytearray都是按照字节操作的,需要转换为整数才能进行位运算,将3个字节看成一个整体转成字节bytes, 34 使用大端模式,如"abc => 0x616263" 35 """ 36 b = int.from_bytes(triple,'big') 37 38 for i in range(18,-1,-6): 39 index = b >> i if i == 18 else b >> i & 0x3F #注意,十六进制0x3F使用而二进制表示为: "11 1111" 40 res.append(bytesBase64[index]) 41 42 43 if r: 44 res[-r:] = b'=' * r #替换等号,从索引-r到末尾使用右边的多个元素依次替换 45 46 return bytes(res) 47 48 49 if __name__ == '__main__': 50 testList = ["a", "`", "ab", "abc", "jason", "yinzhengjie", "尹正杰2019"] 51 for item in testList: 52 print(item) 53 print("自定义的base64编码:{}".format(base64encode(item))) 54 print("使用base64标准库编码:{}".format(base64.b64encode(item.encode()))) 55 print("*" * 50)
2>.实现一个cache装饰器,实现可过期被清楚的功能
缓存的应用场景:
有数据需要频繁使用。
获取数据代价高,即每次获取都需要大量或者较长等待时间。
使用缓存来提高查询速度,用内存空间换取查询,加载时间。cache的应用极广,比如硬件CPU的一级,二级缓存,硬盘自带的缓存空间,软件的redies,varnish集群缓存软件等等。

1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 7 from functools import wraps 8 import time,inspect,datetime 9 10 11 """ 12 缓存装饰器实现 13 """ 14 def jason_cache(duration): 15 def _cache(fn): 16 local_cache = {} # 对不同函数名是不同的cache 17 @wraps(fn) 18 def wrapper(*args, **kwargs): 19 # 使用缓存时才清除过期的key 20 expire_keys = [] 21 for k, (_, stamp) in local_cache.items(): 22 now = datetime.datetime.now().timestamp() 23 if now - stamp > duration: 24 expire_keys.append(k) 25 26 for k in expire_keys: 27 local_cache.pop(k) 28 29 sig = inspect.signature(fn) 30 params = sig.parameters # 参数处理,构建key,获取一个只读的有序字典 31 target = {} # 目标参数字典 32 33 """ 34 param_name = [key for key in params.keys()] 35 #位置参数 36 for i,v in enumerate(args): 37 k = param_name[i] 38 target[k] = v 39 target.update(zip(params.keys(),args)) 40 41 #关键词参数 42 for k,v in kwargs.items(): 43 target[k] = v 44 target.update(kwargs) 45 """ 46 # 位置参数,关键字参数二合一处理 47 target.update(zip(params.keys(), args), **kwargs) 48 49 # 缺省值处理 50 for k in (params.keys() - target.keys()): 51 target[k] = params[k].default 52 53 """ 54 target.update(((k,params[k].default) for k in (params.keys() - target.keys()))) 55 56 for k,v in params.items(): 57 if k not in target.keys(): 58 target[k] = v.default 59 """ 60 key = tuple(sorted(target.items())) 61 62 #待补充,判断是否需要缓存 63 if key not in local_cache.keys(): 64 local_cache[key] = fn(*args, **kwargs),datetime.datetime.now().timestamp() 65 return key, local_cache[key] 66 return wrapper 67 return _cache 68 69 70 71 """ 72 装饰查看函数执行时间 73 """ 74 def logger(fn): 75 76 def wrapper(*args,**kwargs): 77 start = datetime.datetime.now() 78 ret = fn(*args,**kwargs) 79 delta = (datetime.datetime.now() - start).total_seconds() 80 print(fn.__name__,delta) 81 return ret 82 return wrapper 83 84 """ 85 使用多个装饰器,需要注意调用过程和执行过程 86 调用过程: 87 生长洋葱一样,遵循就近原则,即离得近的装饰器先装饰,从内向外。 88 执行过程: 89 剥洋葱一样,从外向里执行 90 """ 91 @logger 92 @jason_cache(10) 93 def add(x,y,z=30): 94 time.sleep(3) 95 return x + y + z 96 97 98 if __name__ == '__main__': 99 result = [] 100 result.append(add(10,20)) 101 result.append(add(10,y=20)) 102 result.append(add(10, 20, 30)) 103 result.append(add(10,z=30,y=20)) 104 result.append(add(x=10,y=20,z=30)) 105 106 for item in result: 107 print(item)
七.装饰器的用途
装饰器是AOP面向切面编程 Aspect Oriented Programming的思想的体现。
面向对象往往需要通过继承或者组合依赖等方式调用一些功能,这些功能的代码往往可能再多个类中出现,例如logger功能代码。这样造成代码的重复,增加了耦合。loggger的改变影响所有其它的类或方法。
而AOP再许哟啊的类或者方法上切下,前后的切入点可以加入增强的功能。让调用者和被调用者解耦,这是一种不修改原来的业务代码,给程序员动态添加功能的技术。例如logger函数就是对业务函数增加日志的功能,而业务函数中应该把业务无关的日志功能剥离干净。
八.装饰器应用场景
日志,监控,权限,审计,参数检查,路由等处理。
这些功能与业务功能无关,是很多都需要的公有的功能,所有适合独立出来,需要的时候,对目标对象进行增强。
简单讲:缺什么,补什么。
本文来自博客园,作者:尹正杰,转载请注明原文链接:https://www.cnblogs.com/yinzhengjie/p/10964821.html,个人微信: "JasonYin2020"(添加时请备注来源及意图备注,有偿付费)
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2018-06-02 Hadoop基础-Apache Avro串行化的与反串行化