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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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 python2 #_*_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函数就是对业务函数增加日志的功能,而业务函数中应该把业务无关的日志功能剥离干净。
八.装饰器应用场景
日志,监控,权限,审计,参数检查,路由等处理。这些功能与业务功能无关,是很多都需要的公有的功能,所有适合独立出来,需要的时候,对目标对象进行增强。
简单讲:缺什么,补什么。
以上是 Python入门篇-装饰器 的全部内容, 来源链接: utcz.com/z/387805.html