Python入门篇-装饰器

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

八.装饰器应用场景

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

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

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

以上是 Python入门篇-装饰器 的全部内容, 来源链接: utcz.com/z/387805.html

回到顶部