赞
踩
对应Cookbook章节 9.1、9.2、9.3 和9.8
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # @Time : 2023/8/4 18:44
- # @Author : Maple
- # @File : 01-函数装饰器入门.py
-
- # Cookbook 9.1、9.2、9.3 和9.8
-
-
- import time
- from functools import wraps
-
- def timethis(func):
- '''
- Decorator that reports the execution time.
- '''
- @wraps(func)
- def wrapper(*args, **kwargs):
- start = time.time()
- result = func(*args, **kwargs)
- end = time.time()
- print(func.__name__, end-start)
- return result
- return wrapper
-
- @timethis
- def countdown(n):
- '''
- Counts down
- '''
- print('hello')
- while n > 0:
- n -= 1
-
- # 多层装饰器
- def decorator1(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- print('Decorator 1')
- return func(*args, **kwargs)
- return wrapper
-
- def decorator2(func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- print('Decorator 2')
- return func(*args, **kwargs)
- return wrapper
-
- @decorator1
- @decorator2
- def add(x, y):
- return x + y
-
- # @wraps装饰func的底层 到底发生了什么?
- # Part1
- """
- # 1.wrapped是被装饰的函数
- # 2.WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__','__annotations__')
- # 3.WRAPPER_UPDATES = ('__dict__',)
- # 4.返回一个 update_wrapper 的偏函数
- def wraps(wrapped,
- assigned = WRAPPER_ASSIGNMENTS,
- updated = WRAPPER_UPDATES):
- Decorator factory to apply update_wrapper() to a wrapper function
-
- return partial(update_wrapper, wrapped=wrapped,
- assigned=assigned, updated=updated)
- """
-
- # Part2
- """
- # 1. wrapper是装饰器被装饰后 返回的函数
- # 2. updated = ('__dict__',)
- def update_wrapper(wrapper,
- wrapped,
- assigned = WRAPPER_ASSIGNMENTS,
- updated = WRAPPER_UPDATES):
- Update a wrapper function to look like the wrapped function
-
- for attr in assigned:
- try:
- # 1. 取出`被装饰`函数的属性
- value = getattr(wrapped, attr)
- except AttributeError:
- pass
- else:
- # 2.将第一步取出来的属性值 赋值给 wrapper,也就是说wrapper的元数据实际上替换成[被装饰的函数]了
- # 以此实现了被装饰函数 元数据的保留
- setattr(wrapper, attr, value)
- for attr in updated:
- # 1.update方法实现 字典值的更新,注意是 后面更新前面的(逻辑: 交叉的key,用后面的值更新前面的; 只存在于后面的key,添加到前面的字典 )
- # 2.如果wrapped 有 ('__dict__',)属性[Tips个人理解:类才有dict属性,方法没有这个属性],那么就把wrapper的dict属性也替换成wrapped的
- # 3.如果wrapped 没有 dict属性, getattr去默认值{},而空字典 对wrapper.__dict__'的值 不会有任何影响
- getattr(wrapper, attr).update(getattr(wrapped, attr, {}))
- return wrapper
- """
-
-
-
- if __name__ == '__main__':
-
- # 1. 简单装饰器测试
- countdown(1000000)
- print('===================')
-
-
-
- # 2.解除装饰器
- # 2-1 方法之一
- """
- 1.适用场景: 在函数已经有装饰器的情况下,但是又要单独测试函数本身的场景
- 2.必要条件1: 有@wraps修饰
- 2.必要条件2:只有一个装饰器,多个装饰器的情况解除之后的测试结果不可预测
- """
- # 通过__wrapped__直接访问被包装的函数:即countdown自身(不会走装饰器里面的逻辑)
- countdown.__wrapped__(1000)
-
- # 2-2 方法之二
-
- print('===================')
- countdown = countdown.__wrapped__
- countdown(100000)
-
- # 2-3 多个装饰器的解除测试
- print('===================')
- add = add.__wrapped__
- result = add(2,3)
- print('result = {}'.format(result)) # result = 5
- """
- output:
- Decorator 2 ,也就是说内层的装饰器并未解除
- result = 5
- """
-
- print(add.__module__) # __main__
- print(add.__name__) # add

对应cookbook章节 9.4 和 9.6
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # @Time : 2023/8/4 19:08
- # @Author : Maple
- # @File : 02-函数装饰器传参.py
- import logging
- from functools import wraps, partial
-
- """
- 对应cookbook
- 9.4 和 9.6
- """
-
- def logged(level,name=None,message=None):
-
- def decorater(func):
- logname = name if name else func.__module__
- log = logging.getLogger(logname)
- logmsg = message if message else func.__name__
-
- @wraps(func)
- def wrapper(*args,**kwargs):
- log.log(level,logmsg)
- # print('{}:{}'.format(logname,logmsg))
- return func(*args,**kwargs)
-
- return wrapper
-
- return decorater
-
-
- # 正常调用1
- @logged(logging.DEBUG)
- def add(x,y):
- return x + y
-
- # 正常调用2
- @logged(logging.CRITICAL,'example')
- def spam():
- print('Spam')
-
-
-
- # 正常调用3
- # @logged()
- def sub(x,y):
- return x - y
-
- # 非正常调用,会报错
- @logged
- def sub(x,y):
- return x - y
-
- # 因为如上方式等价于
- sub = logged(sub) # 即第一个参数是被装饰函数本身,但是logged实际需要是接收的是level,name和message三个参数
- # sub()
-
- # 如何改进?
-
- def logged2(func = None,*, level=logging.DEBUG,name=None,message=None):
- if func is None:
- return partial(logged2,level = level,name = name,message = message)
-
- logname = name if name else func.__module__
- log = logging.getLogger(logname)
- logmsg = message if message else func.__name__
-
-
- @wraps(func)
- def wrapper(*args,**kwargs):
- log.log(level,logmsg)
- print('{}:{}'.format(logname,logmsg))
- return func(*args,**kwargs)
-
- return wrapper
-
- """
- 如下方式等价于:
- sub2 = logged2(sub2) # 即第一个参数是被装饰函数本身
- # 此时sub2其实已经变成wrapper
- sub2()
- 由于第一个参数不为空,所以直接走logger2 if后面的逻辑
- """
- # 1. 没有任何参数传递(注意这里并没有使用(),和装饰器的`普通用法`保持一致)
- @logged2
- def sub2(x,y):
- return x - y
-
-
-
- """
- 执行流程
- 1. 由于logger2中func参数未传入,所以为None,会返回一个偏函数partial(logged2,level = level,name = name,message = message),假设是s.所以第一句执行完成后,变成
- @s
- def add2(x,y)
- 2. add2 = s(add2),此时func参数不为None,所以直接走if后面的逻辑。最终返回的add2其实已经是 wrapper 了
- 3. add2() --> 本质上是执行wrapper()
- """
- # 2. 如果传递参数
- @logged2(level=logging.CRITICAL,name='add2',message='add2被执行了')
- def add2(x,y):
- return x + y
-
-
-
- if __name__ == '__main__':
-
-
- print('======logger 正常有参测试==========')
- result = add(1,2) #3
- print(result) #Spam
-
- spam()
-
- # result2 = sub(3,10)
- # print(result2)
-
- print('=====logger2 sub2无参测试===========')
-
- result2 = sub2(10,2)
- # __main__:sub2
- print('result2 = {}'.format(result2)) # result2 = 8
-
- print('=====logger2 add2有参测试===========')
-
- result3 = add2(10, 2)
- #add2:add2被执行了
- print('result3 = {}'.format(result3)) # result3 = 12

对应cookbook章节 9.5
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # @Time : 2023/8/16 21:17
- # @Author : Maple
- # @File : 05-可自定义属性的装饰器.py
- import logging
- from functools import partial, wraps
-
-
- """
- cookbook 9.5
- """
- def attach_wrapper(obj,func = None):
- if func is None:
- return partial(attach_wrapper,obj)
-
- setattr(obj,func.__name__,func)
- return func
-
- def logged(level,name = None ,msg = None):
-
- def decorate(func):
- log_name = name if name else func.__module__
- log = logging.getLogger(log_name)
- log_msg = msg if msg else func.__name__
-
- @wraps(func)
- def wrapper(*args,**kwargs):
- log.log(level, log_msg)
- print('level:',level,'log_msg',log_msg)
- func(*args,**kwargs)
-
- # 通过装饰器,给wrapper设置一个属性,属性名字就是被装饰函数:即set_level,然后属性值是 该函数的引用
- # 所以调用wrapper的set_level属性时,本质上就是在调用该方法。然后该方法会修改 level的值
- @attach_wrapper(wrapper)
- def set_level(new_level):
- # 申明该变量不是函数set_level中的局部变量,而是函数之外 logged函数中的参数
- nonlocal level
- level = new_level
-
- @attach_wrapper(wrapper)
- def set_message(new_msg):
- # 注意这里不要去改msg的值,而是要去修改log_msg
- # 1. 因为方法调用set_message的时候,首先跳到这里修改局部变量的值
- # 2. 然后跳到 wrapper方法,所以 如果修改msg的值, 由于[log_msg = msg if msg else func.__name__]这段代码(在wrapper上面)根本不会执行,所以msg的值也就无法改变
- nonlocal log_msg
- log_msg = new_msg
-
- return wrapper
-
- return decorate
-
-
- @logged(level=logging.DEBUG,name = '罗二',msg="你也有今天")
- def add(a,b):
- return a + b
-
- if __name__ == '__main__':
-
-
- result = add(1,2)
- print('----------------')
-
- # 执行顺序
- ## 1. add此时是 wrapper
- ## 2. 而 set_level是wrapper中的一个实例属性,且对用的值是fun set_level
- ## 3. 因此以下语句会执行set_level逻辑,将level 修改为logging.INFO)
-
- add.set_level(logging.INFO)
- ## 4. 执行 wrapper中的逻辑:log.log(level, log_msg).....,注意此时level已经被修改成logging.INFO
- result = add(1, 2)
-
- print('----------------')
- add.set_message('好汉不提当年勇')
- result = add(1, 2)

对应cookbook章节 9.7
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # @Time : 2023/8/5 0:53
- # @Author : Maple
- # @File : 03-强制参数类型检测.py
-
- from inspect import signature
- from functools import wraps
-
- """
- 对应cookbook 9.7
- """
-
- def typeassert(*ty_args,**ty_kwargs):
-
- def decorate(func):
- # 如果是优化执行模式,不进行参数类型检查
- if not __debug__:
- return func
-
- sig = signature(func)
-
- # 自定义的参数类型限制条件,Sample:OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
- bound_types = sig.bind_partial(*ty_args,**ty_kwargs).arguments
- print('bound_types',bound_types) # OrderedDict([('x', <class 'int'>), ('z', <class 'int'>)])
-
- @wraps(func)
- def wrapper(*args,**kwargs):
- # 被装饰函数所有实参的sig 字典,bound_values.arguments:OrderedDict([('x', 1), ('y', 2), ('z', 3)])
- bound_values = sig.bind(*args,**kwargs) # <BoundArguments (a=1, b=2, c=1)>
-
- for name,value in bound_values.arguments.items():
- if name in bound_types:
- if not isinstance(value,bound_types[name]):
- raise TypeError('参数{} Expect type {}'.format(name, bound_types[name]))
-
- return func(*args,**kwargs)
-
- return wrapper
-
- return decorate
-
- @typeassert(int,c=str)
- def add(a,b,c='maple'):
- print(a,b,c)
-
-
- if __name__ == '__main__':
-
- sig = signature(add)
-
- bound_values = sig.bind(1,2,c = 1)
- print(bound_values.arguments)
-
-
- add(1,2,'Maple')
- try:
- add('I', 2, 'Maple') # TypeError: 参数a Expect type <class 'int'>
-
- except TypeError as e:
- print(e)
-
- try:
- add(1, 3, 3)
- except TypeError as e:
- print(e) # 参数c Expect type <class 'str'>

对应cookbook章节:9.8 和 9.9
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- # @Time : 2023/8/5 1:55
- # @Author : Maple
- # @File : 04-装饰器类.py
-
- """
- 对应cookbook章节:9.8 和 9.9
- """
-
-
- # 1. 将装饰器定义为类的一部分
- import types
- from functools import wraps
-
-
- class A:
-
- def decorator1(self,func):
- @wraps(func)
- def wrapper(*args,**kwargs): # 内部的wrapper方法通常不需要传入self 或者 cls
- print('decorator1')
- return func(*args,**kwargs)
-
- return wrapper
-
- @classmethod
- def decorator2(cls, func):
- def wrapper(*args, **kwargs):
- print('decorator2')
- return func(*args, *kwargs)
-
- return wrapper
-
- a = A()
-
- @a.decorator1
- def add(a,b):
- return a + b
-
- @A.decorator2
- def sub(a,b):
- return a - b
-
-
- """
- @property 的getter(), setter(), deleter(),其实就是在类中定义的三个装饰器
- """
-
- class Person:
-
- def __init__(self,name):
- self._first_name = name
-
- # 创建一个类的实例
- first_name = property()
-
- # 通过类的实例中的getter方法,实现装饰器的功能
- @first_name.getter
- def first_name(self):
- return self._first_name
-
- @first_name.setter
- def first_name(self,value):
- if not isinstance(value,str):
- raise TypeError('Expect str type')
-
-
- # 2. 将类定义为装饰器
- # !!!get 方法:还是没有彻底理解
- class Profiled:
- def __init__(self,func):
- wraps(func)(self)
- self.ncalls = 0
-
- def __call__(self, *args, **kwargs):
- self.ncalls += 1
- print('装饰器类被调用了{}次'.format(self.ncalls))
- return self.__wrapped__(*args,**kwargs)
-
- def __get__(self, instance, cls):
- if instance is None:
- return self
- else:
- # 该方法是为实例/类动态增加方法,第一个参数应该是方法,但这里是Profiled类的一个实例, 不懂~~
- ## 返回结果是
- print('get 方法被调用')
- print('self',self)
- print('instance:',instance)
- print('cls:',cls)
- return types.MethodType(self,instance)
-
- @Profiled
- def mul(a,b):
- return a * b
-
-
- class Spam:
- @Profiled
- def div(self,a,b):
- return a / b
-
- class Spam2:
- def div(self,a,b):
- return a / b
-
-
- if __name__ == '__main__':
-
- result1 = add(1,2)
- print(result1)
-
-
- print('=================')
- result2 = sub(10,2)
- print(result2)
-
- print('=================')
- p = Person('Maple')
- print(p.first_name)
- # p.first_name = 12
-
- print('======将类定义为装饰器测试===========')
- mul(1,2) # 装饰器类被调用了1次
- mul(1,2) # 装饰器类被调用了2次
- print(mul.ncalls) # 2
-
- print('---------------------------')
- """
- 执行步骤:
- 1. 实例化Profiled,Profiled 装饰Spam,然后才实例化Spam,得到s
- 2. 调用s.div(10,2),进到Profiled类的get方法,返回一个Spam的bound方法
- 3. 调用Profiled的call 方法,执行self.__wrapped__(*args,**kwargs),其实self.__wrapped__为Spam的bound方法(Spam.div,就是被装饰的函数)
- ,执行spam.div()方法,得到最终结果
-
- """
- s = Spam()
- print('s',s)
- s.div(10,2) # 装饰器类被调用了1次
- s.div(15, 5) # 装饰器类被调用了1次
- print(s.div.ncalls) # 2
-
- # 装饰器的用法等价于:
- print('======装饰器的用法等价测试===========')
- s2 = Spam2()
- s2 = Profiled(s2.div)
- print(s2(15,3))

Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。