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函数就是对业务函数增加日志的功能,而业务函数中应该把业务无关的日志功能剥离干净。

 

八.装饰器应用场景

日志,监控,权限,审计,参数检查,路由等处理。

这些功能与业务功能无关,是很多都需要的公有的功能,所有适合独立出来,需要的时候,对目标对象进行增强。

简单讲:缺什么,补什么。

 

posted @ 2019-06-02 22:30  尹正杰  阅读(378)  评论(0编辑  收藏  举报