cookbook_元编程
问题:给函数加一个外包装层,已添加额外的处理,例如,记录日志,计时统计等 解决方案:可以定义一个装饰器来包装函数
问题:当一个函数被装饰器装饰时,一些重要的元数据比如:函数名、文档字符串、函数注解以及调用签名都丢失了 解决方案:每当定义一个装饰器时应该总是记得为底层的包装函数添加functools库中的@wraps装饰器
#问题:当一个函数被装饰器装饰时,一些重要的元数据比如:函数名、文档字符串、函数注解以及调用签名都丢失了 #解决方案:每当定义一个装饰器时应该总是记得为底层的包装函数添加functools库中的@wraps装饰器 import time import functools def timethis(func): @functools.wraps(func) def wrapper(*args,**kwargs): start_time = time.time() result = func(*args,**kwargs) end_time = time.time() print(func.__name__,end_time-start_time) return result return wrapper @timethis def mysleep(num:int): """ 原函数注释文档 :param num: :return: """ time.sleep(num) print("我是原函数") mysleep(3) print(mysleep.__name__) print(mysleep.__doc__) print(mysleep.__annotations__) #如果装饰器使用@functools.wraps(func) 装饰,我们就可以使用下面的方法获取到原函数!!! mysleep.__wrapped__(3)
问题: 我们已经把装饰器添加到函数上了,但是想撤销它,访问未经包装的原函数。 解决方案:假设装饰器已经实现了@warps(func),一般来说我们可以通过访问__wrapped__属性来获取到原函数
问题:我们想编写一个可接收参数的装饰器函数 解决方案:假设我们想编写一个为函数添加日志功能的装饰器,但是又允许用户指定日志的等级以及一些其他的细节操作作为参数。
import logging import functools def logged(level,name=None,message=None): def decorate(func): logname = name if name else func.__module__ log = logging.getLogger(logname) logmsg = message if message else func.__name__ @functools.wraps(func) def wrapper(*args,**kwargs): log.log(level,message) return func(*args,**kwargs) return wrapper return decorate
问题:我们想编写一个装饰器来包装函数,但是可以让用户调整装饰器的属性,这样在运行时就能够控制装饰器的行为
from functools import wraps,partial import logging 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,message=None): def decorate(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) return func(*args,**kwargs) @attach_wrapper(wrapper) def set_level(newlevel): nonlocal level level = newlevel @attach_wrapper(wrapper) def set_message(newmsg): nonlocal logmsg logmsg = newmsg return wrapper return decorate logging.basicConfig(level=logging.DEBUG) @logged(logging.DEBUG) def add(x,y): return x + y add(2,5) add.set_message("Add called") add(3,8)
问题:我们想编写一个单独的装饰器,使其既可以像@decorator 这样不带参数,也可以像@decorator(x,y,z)这样接收可选参数
from functools import wraps,partial import logging def logged(func=None,*,level=logging.DEBUG,name=None,message=None): if func is None: return partial(logged,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) return func(*args,**kwargs) return wrapper @logged def add(x,y): logging.debug("hahahah") return x+y #没有参数时,装饰器就相当于:logged(func),所以装饰器的第一个参数就是func,其他都是可选参数 @logged(level=logging.CRITICAL,name="example") def spam(): print("spam!!!") #有参数时,装饰器就相当于logged(level=logging.DEBUG,name="example")(spam) #巧妙的利用functools.partial 将构建好的方法返回 add(1,2) spam()
问题:我们想为函数参数添加强制类型检查功能,将其作为一种断言或者与调用者之间的契约
from inspect import signature from functools import wraps def typeassert(*ty_args,**ty_kwargs): def decorate(func): if not __debug__: return func sig = signature(func)#获取func的参数签名(x,y,z) bound_types = sig.bind_partial(*ty_args,**ty_kwargs).arguments# 参数签名与类型参数做映射 [("x",<class "int">),("z",<class "int">)] @wraps(func) def wrapper(*args,**kwargs): bound_values = sig.bind(*args,**kwargs).arguments# 参数签名与函数参数做映射 for name,value in bound_values.items(): if name in bound_types:#判断参数是否有类型限制 if not isinstance(value,bound_types[name]): raise TypeError("Argument {} must be {}".format(name,bound_types[name])) return func(*args,**kwargs) return wrapper return decorate class A(): def a(self): print("a") @typeassert(int,A,z=int) def add(x,y,z): print(x,y,z) return x add(1,A(),3) #想法:参数类型的限制可以使用在参数处理方法中,对前端接收的参数进行检查,也可以使用在一些需要限制传入参数类型的地方 #注:此装饰器一个微妙的地方,只检查传递的参数,如果是默认参数,没有进行传递,参数类型不进行检查 @typeassert(int,list) def bar(x,items=None): if items is None: items = [] items.append(x) return items print(bar(2))
问题: 我们想在类中定义一个装饰器,并将其作用到其他函数或方法上
from functools import wraps class A: def decorator1(self,func): @wraps(func) def wrapper(*args,**kwargs): print("decorator 1") return func(*args,**kwargs) return wrapper @classmethod def decorator2(cls,func): @wraps(func) def wrapper(*args,**kwargs): print("decorator 2") return func(*args,**kwargs) return wrapper #思考:@property 实际上是一个拥有 getter(),setter(),deleter()方法的类,每一个方法都可作为一个装饰器 #几个装饰器都可以操纵实例的状态,因此,如果需要装饰器在背后记录或合并信息,这是一个很明智的方法。
问题: 我们想用装饰器来包装函数,但是希望得到的结果是一个可调用的实例。我们需要装饰器既能在类中工作,也可以在类外部使用 解决方案:要把装饰器定义成类实例,需要确保在类中实现__call__()和__get__()方法
import types from functools import wraps class Profield: def __init__(self,func): wraps(func)(self) self.ncalls = 0 def __call__(self, *args, **kwargs): self.ncalls +=1 return self.__wrapped__(*args,**kwargs) def __get__(self, instance, cls): if instance is None: return self else: return types.MethodType(self,instance) #该装饰器相当于为函数添加一个属性 ncalls
问题:我们想在类或者静态方法上应用装饰器 解决方案:将装饰器作用到类和静态方法上是简单而直接的,但是要保证装饰器在应用的时候需要放在@classmethod 和 @staticmethod 之前,示例如下:
import time from functools import wraps def timethis(func): @wraps(func) def wrapper(*args,**kwargs): start = time.time() r = func(*args,**kwargs) end = time.time() print(end-start) return r return wrapper
@classmethod 和 @staticmethod 装饰器并不会返回一个可执行对象,所以装饰器都要放在他们下面!!!
问题:我们想编写一个装饰器,为被包装的函数添加额外的参数,但是添加的参数不能影响到该函数已有的调用约定 解决方案:
from functools import wraps def optinoal_debug(func): @wraps(func) def wrapper(*args,debug=False,**kwargs): if debug: print("Calling",func.__name__) return func(*args,**kwargs) return wrapper
函数中的一部分参数被装饰器解析所用,剩下参数给到函数,可以用被包装函数的参数来控制装饰器的行为
#问题:我们想检查或改写一部分类的定义,以此来修改类的行为,但是不想通过继承或者元类的方式来做 #解决方案:
def log_getattribute(cls): orig_getattribute = cls.__getattribute__ def new_getattribute(self,name): print("getting",name) return orig_getattribute(self,name) cls.__getattribute__ = new_getattribute return cls @log_getattribute class A: def __init__(self,x): self.x = x def spam(self): pass a = A(42) a.x
可以通过此方法对类的属性做监控