• cookbook_元编程


    1给函数添加一个包装

    问题:给函数加一个外包装层,已添加额外的处理,例如,记录日志,计时统计等
    
    
    解决方案:可以定义一个装饰器来包装函数

    2编写装饰器时如何保存函数的元数据

    问题:当一个函数被装饰器装饰时,一些重要的元数据比如:函数名、文档字符串、函数注解以及调用签名都丢失了
    解决方案:每当定义一个装饰器时应该总是记得为底层的包装函数添加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)
    
    

    3对装饰器进行解包装

    问题: 我们已经把装饰器添加到函数上了,但是想撤销它,访问未经包装的原函数。
    
    解决方案:假设装饰器已经实现了@warps(func),一般来说我们可以通过访问__wrapped__属性来获取到原函数

    4定义一个可接收参数的装饰器

    问题:我们想编写一个可接收参数的装饰器函数
    解决方案:假设我们想编写一个为函数添加日志功能的装饰器,但是又允许用户指定日志的等级以及一些其他的细节操作作为参数。
    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

     5定义一个属性可由用户修改的装饰器

    问题:我们想编写一个装饰器来包装函数,但是可以让用户调整装饰器的属性,这样在运行时就能够控制装饰器的行为
    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)

    6定义一个能接收可选参数的装饰器

    问题:我们想编写一个单独的装饰器,使其既可以像@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()

    7利用装饰器对函数参数强制执行类型检查

    问题:我们想为函数参数添加强制类型检查功能,将其作为一种断言或者与调用者之间的契约
    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))

    8在类中定义装饰器

    问题: 我们想在类中定义一个装饰器,并将其作用到其他函数或方法上
    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()方法的类,每一个方法都可作为一个装饰器
    #几个装饰器都可以操纵实例的状态,因此,如果需要装饰器在背后记录或合并信息,这是一个很明智的方法。

    9把装饰器定义成类

    问题: 我们想用装饰器来包装函数,但是希望得到的结果是一个可调用的实例。我们需要装饰器既能在类中工作,也可以在类外部使用
    
    解决方案:要把装饰器定义成类实例,需要确保在类中实现__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
    10把装饰器作用到类和静态方法上
    问题:我们想在类或者静态方法上应用装饰器
    
    解决方案:将装饰器作用到类和静态方法上是简单而直接的,但是要保证装饰器在应用的时候需要放在@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 装饰器并不会返回一个可执行对象,所以装饰器都要放在他们下面!!!

    11编写装饰器为被包装函数添加参数

    问题:我们想编写一个装饰器,为被包装的函数添加额外的参数,但是添加的参数不能影响到该函数已有的调用约定
    
    解决方案:
    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
    函数中的一部分参数被装饰器解析所用,剩下参数给到函数,可以用被包装函数的参数来控制装饰器的行为

    12利用装饰器给函数定义打补丁

    #问题:我们想检查或改写一部分类的定义,以此来修改类的行为,但是不想通过继承或者元类的方式来做
    
    
    #解决方案:
    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
    可以通过此方法对类的属性做监控
  • 相关阅读:
    cuda9.0 中不存在libnppi.so
    深度学习训练踩坑记
    采用代理之后,pip 运行报错socks
    摄像机模型
    段错误:使用opencv打开视频流
    ffmpeg+cuda+opencv
    pip install xxx -i https://pypi.tuna.tsinghua.edu.cn/simple
    【TCP/IP详解】BOOTP:引导程序协议
    【TCP/IP详解】TFTP:简单文件传送协议
    【TCP/IP详解】IGMP Internet组管理协议
  • 原文地址:https://www.cnblogs.com/jiaojianglong/p/11260200.html
Copyright © 2020-2023  润新知