zoukankan      html  css  js  c++  java
  • Python 元编程

    1.为函数添加包装器

    总是存在这样的场景,在一个函数执行前后需要做一些操作处理,常见于日志创建、权限认证或者性能分析等。但有一个问题存在,那就是被装饰的函数,其元信息会丢失,函数引用会指向装饰器的返回值(函数)引用

    这里介绍functools模块下的wraps函数, 能够避免函数元信息丢失的情况发生, 保留原始函数的元数据。

    from functools import wraps
    
    def outer_nowraps(func):
        def inner(*args, **kwargs):
            pass
            return func(*args, **kwargs)
        return inner
    
    def outer_wraps(func):
        @wraps(func)
        def inner(*args, **kwargs):
            pass
            return func(*args, **kwargs)
        return inner
    
    @outer_nowraps
    def handle_nowraps(*args, **kwargs00):
        pass
    
    @outer_wraps
    def handle_wraps(*args, **kwargs):
        pass
    
    if __name__ == '__main__':
        print(handle_nowraps)  
        # <function outer_nowraps.<locals>.inner at 0x0000026363980620> 指向装饰器返回值(函数)
        help(handle_nowraps)  
        # Help on function inner in module __main__:inner(*args, **kwargs)
        print(handle_wraps)  
        # <function handle_wraps at 0x0000026363980730> 指向自身
        help(handle_wraps)  
        # Help on function handle_wraps in module __main__:handle_wraps(*args, **kwargs)
    

    此外,值得说明的是,装饰器就是一个函数,它接收一个函数作为参数返回一个新的函数(利用partial实现),例如以下情况是等效的

    @outer_wraps
    def handle_wraps(*args, **kwargs):
        pass
    
    handle_wraps = outer_wraps(handle_wraps)
    

    对于类而言,诸如@staticmethod, @classmethod, @property原理也是一样的,例如以下情况是等效的

    class A:
        @classmethod
        def method(cls):
            pass
        
    class A:
        def method(cls):
            pass
        method = classmethod(method)
    

    2.解除装饰器

    如果一个装饰器(内部被wraps包装)已经作用在了一个函数上,如果想撤销它,直接访问原始的未包装的那个函数,可以使用该函数对象的__wrapped__属性来实现,当然并不是所有的装饰器内部均是使用wraps进行包装,例如常见的@staticmethod@classmethod@property等等,被这些装饰的函数是不具备解除装饰器的能力的。


    3. 定义带参数的装饰器

    对于logging模块来说,日志常常分为DEBUGINFOWARNINGERRORCRITICAL等,如果此时要实现一个装饰器,在不同函数上应用不同的装饰级别,就可以考虑使用一个带参数的装饰器来完成。

    from functools import wraps
    import logging
    
    def logged(level, name=None, message=None):
        """实现在不同函数上自定义日志级别及日志输出"""
        
        def decorator(func: function):
            log_name = name if name else func.__module__
            log = logging.getLogger(log_name)
            log_msg = message if message else func.__name__
            
            @wraps(func)
            def wrapper(*args, **kwargs):
                log.log(level, log_msg)
                return func(*args, **kwargs)
            return wrapper
        
        return decorator
    
    @logged(logging.DEBUG)
    def add(x, y):
        return x + y
    
    @logged(logging.CRITICAL, "example")
    def spam():
        print("Spam")
    

    刚刚我们了解到了如何定义不带参数的装饰器,以及如何使用等效的语法表示,那么对于这种有参装饰器,又如何在语法上等效表示呢?logged(logging.DEBUG)实际上返回了一个decorator的引用,所以等效表示语法如下:

    add = logged(logging.DEBUG)(add) # 函数引用
    

    4.可自定义属性的装饰器

    这也正是Python作为面向对象语言的高级特性,在装饰器返回时,实际上是一个函数引用被接收,那么,这个函数也是function对象,一切对象都可以动态的自定义添加属性,由此便可以实现操作最内层函数引用的属性的方式,来动态的改变装饰器最外层作用域的变量(nonlocal)。

    from functools import wraps
    from functools import partial
    import logging
    
    def modify_wrapper(obj, func=None):
        if func is None:
            return partial(modify_wrapper, obj)
        setattr(obj, func.__name__, func)
        return func
    
    def logged(level, name=None, message=None):
        """实现在不同函数上自定义日志级别及日志输出"""
        def decorator(func):
            log_name = name if name else func.__module__
            log = logging.getLogger(log_name)
            log_msg = message if message else func.__name__
    
            @wraps(func)
            def wrapper(*args, **kwargs):
                log.log(level, log_msg)
                return func(*args, **kwargs)
    
            @modify_wrapper(wrapper)
            def set_level(newlevel):
                nonlocal level
                level = newlevel
            # 1. modify_wrapper(wrapper)返回一个partial(modify_wrapper, obj),固定了obj(即wrapper对象)
            # 2. 返回的partial对象接收了一个set_level函数对象参数(未固定)
            # 3. setattr(obj, func.__name__, func)为obj(即wrapper对象)添加了func(即set_level属性)
            # 4. 返回的仍然是set_level这一函数引用
            # 上述4步的作用就是为wrapper赋以set_level这一函数引用作为其属性
            # set_level = modify_wrapper(wrapper)(set_level)
    
            @modify_wrapper(wrapper)
            def set_message(new_msg):
                nonlocal log_msg
                log_msg = new_msg
            # 理由同上
    
            return wrapper
    
        return decorator
    
    @logged(logging.DEBUG)
    def add(x, y):
        return x + y
    
    @logged(logging.CRITICAL, "example")
    def spam():
        print("Spam")
    
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.DEBUG)
        print(add(1, 2))  # 输出: DEBUG:__main__:add  3
        add.set_message("set msg")
        print(add(3, 4))  # 输出: DEBUG:__main__:set msg  7
        add.set_level(logging.CRITICAL)
        print(add(1, 4))  # 输出: CRITICAL:__main__:set msg  5
    

    上述代码的精妙之处就在于,

    1.partial以装饰器(modify_wrapper)自身展开固定(固定参数是装饰器(logged)内部wrapper函数对象)。

    2.当再次调用modify_wrapper时候(即modify_wrapper(wrapper)(set_level)时,不定参数funcset_level传递进来),在对wrapper完成属性绑定后,返回了set_level函数对象,并等待继续调用

  • 相关阅读:
    R语言数据读入函数read.table
    R语言最小浮点数
    R语言randomForest包实现随机森林——iris数据集和kyphosis数据集
    R语言 rwordseg包的下载
    R语言AMORE包实现BP神经网络——German数据集
    R语言清除内存,无法分配大小为324.5 Mb的矢量
    R语言多重共现性的检测
    Machine Learning for hackers读书笔记(六)正则化:文本回归
    Machine Learning for hackers读书笔记(五)回归模型:预测网页访问量
    完整的Django入门指南学习笔记4
  • 原文地址:https://www.cnblogs.com/kisun168/p/11651232.html
Copyright © 2011-2022 走看看