当前位置:   article > 正文

Python3-Cookbook(第九章) - 元编程Part1

Python3-Cookbook(第九章) - 元编程Part1

一、函数装饰器入门

    对应Cookbook章节 9.1、9.2、9.3 和9.8

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time : 2023/8/4 18:44
  4. # @Author : Maple
  5. # @File : 01-函数装饰器入门.py
  6. # Cookbook 9.1、9.2、9.3 和9.8
  7. import time
  8. from functools import wraps
  9. def timethis(func):
  10. '''
  11. Decorator that reports the execution time.
  12. '''
  13. @wraps(func)
  14. def wrapper(*args, **kwargs):
  15. start = time.time()
  16. result = func(*args, **kwargs)
  17. end = time.time()
  18. print(func.__name__, end-start)
  19. return result
  20. return wrapper
  21. @timethis
  22. def countdown(n):
  23. '''
  24. Counts down
  25. '''
  26. print('hello')
  27. while n > 0:
  28. n -= 1
  29. # 多层装饰器
  30. def decorator1(func):
  31. @wraps(func)
  32. def wrapper(*args, **kwargs):
  33. print('Decorator 1')
  34. return func(*args, **kwargs)
  35. return wrapper
  36. def decorator2(func):
  37. @wraps(func)
  38. def wrapper(*args, **kwargs):
  39. print('Decorator 2')
  40. return func(*args, **kwargs)
  41. return wrapper
  42. @decorator1
  43. @decorator2
  44. def add(x, y):
  45. return x + y
  46. # @wraps装饰func的底层 到底发生了什么?
  47. # Part1
  48. """
  49. # 1.wrapped是被装饰的函数
  50. # 2.WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
  51. # 3.WRAPPER_UPDATES = ('__dict__',)
  52. # 4.返回一个 update_wrapper 的偏函数
  53. def wraps(wrapped,
  54. assigned = WRAPPER_ASSIGNMENTS,
  55. updated = WRAPPER_UPDATES):
  56. Decorator factory to apply update_wrapper() to a wrapper function
  57. return partial(update_wrapper, wrapped=wrapped,
  58. assigned=assigned, updated=updated)
  59. """
  60. # Part2
  61. """
  62. # 1. wrapper是装饰器被装饰后 返回的函数
  63. # 2. updated = ('__dict__',)
  64. def update_wrapper(wrapper,
  65. wrapped,
  66. assigned = WRAPPER_ASSIGNMENTS,
  67. updated = WRAPPER_UPDATES):
  68. Update a wrapper function to look like the wrapped function
  69. for attr in assigned:
  70. try:
  71. # 1. 取出`被装饰`函数的属性
  72. value = getattr(wrapped, attr)
  73. except AttributeError:
  74. pass
  75. else:
  76. # 2.将第一步取出来的属性值 赋值给 wrapper,也就是说wrapper的元数据实际上替换成[被装饰的函数]了
  77. # 以此实现了被装饰函数 元数据的保留
  78. setattr(wrapper, attr, value)
  79. for attr in updated:
  80. # 1.update方法实现 字典值的更新,注意是 后面更新前面的(逻辑: 交叉的key,用后面的值更新前面的; 只存在于后面的key,添加到前面的字典 )
  81. # 2.如果wrapped 有 ('__dict__',)属性[Tips个人理解:类才有dict属性,方法没有这个属性],那么就把wrapper的dict属性也替换成wrapped的
  82. # 3.如果wrapped 没有 dict属性, getattr去默认值{},而空字典 对wrapper.__dict__'的值 不会有任何影响
  83. getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
  84. return wrapper
  85. """
  86. if __name__ == '__main__':
  87. # 1. 简单装饰器测试
  88. countdown(1000000)
  89. print('===================')
  90. # 2.解除装饰器
  91. # 2-1 方法之一
  92. """
  93. 1.适用场景: 在函数已经有装饰器的情况下,但是又要单独测试函数本身的场景
  94. 2.必要条件1: 有@wraps修饰
  95. 2.必要条件2:只有一个装饰器,多个装饰器的情况解除之后的测试结果不可预测
  96. """
  97. # 通过__wrapped__直接访问被包装的函数:即countdown自身(不会走装饰器里面的逻辑)
  98. countdown.__wrapped__(1000)
  99. # 2-2 方法之二
  100. print('===================')
  101. countdown = countdown.__wrapped__
  102. countdown(100000)
  103. # 2-3 多个装饰器的解除测试
  104. print('===================')
  105. add = add.__wrapped__
  106. result = add(2,3)
  107. print('result = {}'.format(result)) # result = 5
  108. """
  109. output:
  110. Decorator 2 ,也就是说内层的装饰器并未解除
  111. result = 5
  112. """
  113. print(add.__module__) # __main__
  114. print(add.__name__) # add

二、函数装饰器传参

  对应cookbook章节 9.4 和 9.6

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time : 2023/8/4 19:08
  4. # @Author : Maple
  5. # @File : 02-函数装饰器传参.py
  6. import logging
  7. from functools import wraps, partial
  8. """
  9. 对应cookbook
  10. 9.4 和 9.6
  11. """
  12. def logged(level,name=None,message=None):
  13. def decorater(func):
  14. logname = name if name else func.__module__
  15. log = logging.getLogger(logname)
  16. logmsg = message if message else func.__name__
  17. @wraps(func)
  18. def wrapper(*args,**kwargs):
  19. log.log(level,logmsg)
  20. # print('{}:{}'.format(logname,logmsg))
  21. return func(*args,**kwargs)
  22. return wrapper
  23. return decorater
  24. # 正常调用1
  25. @logged(logging.DEBUG)
  26. def add(x,y):
  27. return x + y
  28. # 正常调用2
  29. @logged(logging.CRITICAL,'example')
  30. def spam():
  31. print('Spam')
  32. # 正常调用3
  33. # @logged()
  34. def sub(x,y):
  35. return x - y
  36. # 非正常调用,会报错
  37. @logged
  38. def sub(x,y):
  39. return x - y
  40. # 因为如上方式等价于
  41. sub = logged(sub) # 即第一个参数是被装饰函数本身,但是logged实际需要是接收的是level,name和message三个参数
  42. # sub()
  43. # 如何改进?
  44. def logged2(func = None,*, level=logging.DEBUG,name=None,message=None):
  45. if func is None:
  46. return partial(logged2,level = level,name = name,message = message)
  47. logname = name if name else func.__module__
  48. log = logging.getLogger(logname)
  49. logmsg = message if message else func.__name__
  50. @wraps(func)
  51. def wrapper(*args,**kwargs):
  52. log.log(level,logmsg)
  53. print('{}:{}'.format(logname,logmsg))
  54. return func(*args,**kwargs)
  55. return wrapper
  56. """
  57. 如下方式等价于:
  58. sub2 = logged2(sub2) # 即第一个参数是被装饰函数本身
  59. # 此时sub2其实已经变成wrapper
  60. sub2()
  61. 由于第一个参数不为空,所以直接走logger2 if后面的逻辑
  62. """
  63. # 1. 没有任何参数传递(注意这里并没有使用(),和装饰器的`普通用法`保持一致)
  64. @logged2
  65. def sub2(x,y):
  66. return x - y
  67. """
  68. 执行流程
  69. 1. 由于logger2中func参数未传入,所以为None,会返回一个偏函数partial(logged2,level = level,name = name,message = message),假设是s.所以第一句执行完成后,变成
  70. @s
  71. def add2(x,y)
  72. 2. add2 = s(add2),此时func参数不为None,所以直接走if后面的逻辑。最终返回的add2其实已经是 wrapper 了
  73. 3. add2() --> 本质上是执行wrapper()
  74. """
  75. # 2. 如果传递参数
  76. @logged2(level=logging.CRITICAL,name='add2',message='add2被执行了')
  77. def add2(x,y):
  78. return x + y
  79. if __name__ == '__main__':
  80. print('======logger 正常有参测试==========')
  81. result = add(1,2) #3
  82. print(result) #Spam
  83. spam()
  84. # result2 = sub(3,10)
  85. # print(result2)
  86. print('=====logger2 sub2无参测试===========')
  87. result2 = sub2(10,2)
  88. # __main__:sub2
  89. print('result2 = {}'.format(result2)) # result2 = 8
  90. print('=====logger2 add2有参测试===========')
  91. result3 = add2(10, 2)
  92. #add2:add2被执行了
  93. print('result3 = {}'.format(result3)) # result3 = 12

三、可自定义属性的装饰器

对应cookbook章节 9.5

  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time : 2023/8/16 21:17
  4. # @Author : Maple
  5. # @File : 05-可自定义属性的装饰器.py
  6. import logging
  7. from functools import partial, wraps
  8. """
  9. cookbook 9.5
  10. """
  11. def attach_wrapper(obj,func = None):
  12. if func is None:
  13. return partial(attach_wrapper,obj)
  14. setattr(obj,func.__name__,func)
  15. return func
  16. def logged(level,name = None ,msg = None):
  17. def decorate(func):
  18. log_name = name if name else func.__module__
  19. log = logging.getLogger(log_name)
  20. log_msg = msg if msg else func.__name__
  21. @wraps(func)
  22. def wrapper(*args,**kwargs):
  23. log.log(level, log_msg)
  24. print('level:',level,'log_msg',log_msg)
  25. func(*args,**kwargs)
  26. # 通过装饰器,给wrapper设置一个属性,属性名字就是被装饰函数:即set_level,然后属性值是 该函数的引用
  27. # 所以调用wrapper的set_level属性时,本质上就是在调用该方法。然后该方法会修改 level的值
  28. @attach_wrapper(wrapper)
  29. def set_level(new_level):
  30. # 申明该变量不是函数set_level中的局部变量,而是函数之外 logged函数中的参数
  31. nonlocal level
  32. level = new_level
  33. @attach_wrapper(wrapper)
  34. def set_message(new_msg):
  35. # 注意这里不要去改msg的值,而是要去修改log_msg
  36. # 1. 因为方法调用set_message的时候,首先跳到这里修改局部变量的值
  37. # 2. 然后跳到 wrapper方法,所以 如果修改msg的值, 由于[log_msg = msg if msg else func.__name__]这段代码(在wrapper上面)根本不会执行,所以msg的值也就无法改变
  38. nonlocal log_msg
  39. log_msg = new_msg
  40. return wrapper
  41. return decorate
  42. @logged(level=logging.DEBUG,name = '罗二',msg="你也有今天")
  43. def add(a,b):
  44. return a + b
  45. if __name__ == '__main__':
  46. result = add(1,2)
  47. print('----------------')
  48. # 执行顺序
  49. ## 1. add此时是 wrapper
  50. ## 2. 而 set_level是wrapper中的一个实例属性,且对用的值是fun set_level
  51. ## 3. 因此以下语句会执行set_level逻辑,将level 修改为logging.INFO)
  52. add.set_level(logging.INFO)
  53. ## 4. 执行 wrapper中的逻辑:log.log(level, log_msg).....,注意此时level已经被修改成logging.INFO
  54. result = add(1, 2)
  55. print('----------------')
  56. add.set_message('好汉不提当年勇')
  57. result = add(1, 2)

四、强制参数类型检测

对应cookbook章节 9.7
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time : 2023/8/5 0:53
  4. # @Author : Maple
  5. # @File : 03-强制参数类型检测.py
  6. from inspect import signature
  7. from functools import wraps
  8. """
  9. 对应cookbook 9.7
  10. """
  11. def typeassert(*ty_args,**ty_kwargs):
  12. def decorate(func):
  13. # 如果是优化执行模式,不进行参数类型检查
  14. if not __debug__:
  15. return func
  16. sig = signature(func)
  17. # 自定义的参数类型限制条件,Sample:OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
  18. bound_types = sig.bind_partial(*ty_args,**ty_kwargs).arguments
  19. print('bound_types',bound_types) # OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
  20. @wraps(func)
  21. def wrapper(*args,**kwargs):
  22. # 被装饰函数所有实参的sig 字典,bound_values.arguments:OrderedDict([('x', 1), ('y', 2), ('z', 3)])
  23. bound_values = sig.bind(*args,**kwargs) # <BoundArguments (a=1, b=2, c=1)>
  24. for name,value in bound_values.arguments.items():
  25. if name in bound_types:
  26. if not isinstance(value,bound_types[name]):
  27. raise TypeError('参数{} Expect type {}'.format(name, bound_types[name]))
  28. return func(*args,**kwargs)
  29. return wrapper
  30. return decorate
  31. @typeassert(int,c=str)
  32. def add(a,b,c='maple'):
  33. print(a,b,c)
  34. if __name__ == '__main__':
  35. sig = signature(add)
  36. bound_values = sig.bind(1,2,c = 1)
  37. print(bound_values.arguments)
  38. add(1,2,'Maple')
  39. try:
  40. add('I', 2, 'Maple') # TypeError: 参数a Expect type <class 'int'>
  41. except TypeError as e:
  42. print(e)
  43. try:
  44. add(1, 3, 3)
  45. except TypeError as e:
  46. print(e) # 参数c Expect type <class 'str'>

五、类装饰器

对应cookbook章节:9.8 和 9.9
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. # @Time : 2023/8/5 1:55
  4. # @Author : Maple
  5. # @File : 04-装饰器类.py
  6. """
  7. 对应cookbook章节:9.8 和 9.9
  8. """
  9. # 1. 将装饰器定义为类的一部分
  10. import types
  11. from functools import wraps
  12. class A:
  13. def decorator1(self,func):
  14. @wraps(func)
  15. def wrapper(*args,**kwargs): # 内部的wrapper方法通常不需要传入self 或者 cls
  16. print('decorator1')
  17. return func(*args,**kwargs)
  18. return wrapper
  19. @classmethod
  20. def decorator2(cls, func):
  21. def wrapper(*args, **kwargs):
  22. print('decorator2')
  23. return func(*args, *kwargs)
  24. return wrapper
  25. a = A()
  26. @a.decorator1
  27. def add(a,b):
  28. return a + b
  29. @A.decorator2
  30. def sub(a,b):
  31. return a - b
  32. """
  33. @property 的getter(), setter(), deleter(),其实就是在类中定义的三个装饰器
  34. """
  35. class Person:
  36. def __init__(self,name):
  37. self._first_name = name
  38. # 创建一个类的实例
  39. first_name = property()
  40. # 通过类的实例中的getter方法,实现装饰器的功能
  41. @first_name.getter
  42. def first_name(self):
  43. return self._first_name
  44. @first_name.setter
  45. def first_name(self,value):
  46. if not isinstance(value,str):
  47. raise TypeError('Expect str type')
  48. # 2. 将类定义为装饰器
  49. # !!!get 方法:还是没有彻底理解
  50. class Profiled:
  51. def __init__(self,func):
  52. wraps(func)(self)
  53. self.ncalls = 0
  54. def __call__(self, *args, **kwargs):
  55. self.ncalls += 1
  56. print('装饰器类被调用了{}次'.format(self.ncalls))
  57. return self.__wrapped__(*args,**kwargs)
  58. def __get__(self, instance, cls):
  59. if instance is None:
  60. return self
  61. else:
  62. # 该方法是为实例/类动态增加方法,第一个参数应该是方法,但这里是Profiled类的一个实例, 不懂~~
  63. ## 返回结果是
  64. print('get 方法被调用')
  65. print('self',self)
  66. print('instance:',instance)
  67. print('cls:',cls)
  68. return types.MethodType(self,instance)
  69. @Profiled
  70. def mul(a,b):
  71. return a * b
  72. class Spam:
  73. @Profiled
  74. def div(self,a,b):
  75. return a / b
  76. class Spam2:
  77. def div(self,a,b):
  78. return a / b
  79. if __name__ == '__main__':
  80. result1 = add(1,2)
  81. print(result1)
  82. print('=================')
  83. result2 = sub(10,2)
  84. print(result2)
  85. print('=================')
  86. p = Person('Maple')
  87. print(p.first_name)
  88. # p.first_name = 12
  89. print('======将类定义为装饰器测试===========')
  90. mul(1,2) # 装饰器类被调用了1次
  91. mul(1,2) # 装饰器类被调用了2次
  92. print(mul.ncalls) # 2
  93. print('---------------------------')
  94. """
  95. 执行步骤:
  96. 1. 实例化Profiled,Profiled 装饰Spam,然后才实例化Spam,得到s
  97. 2. 调用s.div(10,2),进到Profiled类的get方法,返回一个Spam的bound方法
  98. 3. 调用Profiled的call 方法,执行self.__wrapped__(*args,**kwargs),其实self.__wrapped__为Spam的bound方法(Spam.div,就是被装饰的函数)
  99. ,执行spam.div()方法,得到最终结果
  100. """
  101. s = Spam()
  102. print('s',s)
  103. s.div(10,2) # 装饰器类被调用了1次
  104. s.div(15, 5) # 装饰器类被调用了1次
  105. print(s.div.ncalls) # 2
  106. # 装饰器的用法等价于:
  107. print('======装饰器的用法等价测试===========')
  108. s2 = Spam2()
  109. s2 = Profiled(s2.div)
  110. print(s2(15,3))

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/435082
推荐阅读
相关标签
  

闽ICP备14008679号