• (转)python装饰器二


    Python装饰器进阶之二

    保存被装饰方法的元数据

    什么是方法的元数据

    举个栗子

    def hello():
        print('Hello, World.')
    
    print(dir(hello))

    结果如下:

    ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

    其中:

    __name__: 代表方法的名字
    __doc__: 代表方法的字符串文档(实际上就是"""..."""这种形式的注释)
    __moudle__: 方法所属模块的名字
    __dict__: 属性字典(这个属性在面向对象编程时很重要,用好了能大大节约Python内存的开支)
    __defaults__: 方法参数中,默认参数的值(实际上Python方法的默认参数是创建方法对象的时候就存储在这里的)
    ...
    等等

    以下面一个为例:

    def hello(numa, numb=1, numc=[]):
        """
            Print numa, numb, numc.
        """
        print(numa, numb, numc)
        return True
    
    print(hello.__name__)
    print(hello.__doc__)
    print(hello.__module__)
    print(hello.__dict__)
    print(hello.__defaults__)

    结果如下:

    hello
    
            Print numa, numb, numc.
    
    __main__
    {}
    (1, [])
    [Finished in 0.1s]

    我们可以看到,__doc__实际上就是,方法里面用三引号包裹起来的注释。而__dict__则是方法属性的字典,我们这个方法对象并没有任何的属性,所以说他是空的。

    我们给方法增加一个属性:

    def hello():
        print('Hello, World.')
    
    hello.name = 'XiaoMing'
    print(hello.__dict__)

    结果如下:

    {'name': 'XiaoMing'}

    甚至我们还可以这样:

    def hello():
        print('Hello, World.')
    
    hello.__dict__['name'] = 'XiaoMing'
    print(hello.name)

    结果如下:

    XiaoMing

    同样的,我们的__defaults__属性本身是一个元组,元组是不可变类型的数据结构。但是现在我们的numc使用的是一个列表,那我们是不是可以改变方法默认参数的值呢:

    def hello(numa, numb=1, numc=[]):
        print(numa, numb, numc)
    
    # 一共两个元素,下标1的元素就代表了我们的numc所对应的列表
    hello.__defaults__[1].append('Hello')
    hello(100)

    结果如下:

    100 1 ['Hello']

    所以,在我们方法的默认参数上面,应该避免使用数组这种可变类型的数据结构。因为Python本身把__defaults__属性设置为元组,那就是希望人们无法去修改它,如果我们使用了可变类型的数据结构就违背了Python的本意。

    说了这么多废话,没有进入主题,来看装饰器对方法元数据的影响,上一章的例子:

    def add_cache(func):
        """
            This add_cache
        """
        cache = {}
        def wrap(*args):
            """
                This wrap
            """
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        return wrap
    
    @add_cache
    def fibonacci(n):
        """
            This fibonacci
        """
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    # 实际上返回的对象是wrap方法的对象,所以得到的也是wrap方法的元数据
    print(fibonacci.__name__)
    print(fibonacci.__doc__)

    结果如下:

    wrap
    
                This wrap

    如何保存被装饰方法的元数据不被改变

    这样就存在一个问题,我们的方法被装饰以后,原来的某些东西,我们无法访问了,这肯定是不行的,那我们必须想办法能够在装饰以后还保持某些元数据是原来方法的元数据。

    简单思考以后我们可以这样做:

    def add_cache(func):
        """
            This add_cache
        """
        cache = {}
        def wrap(*args):
            """
                This wrap
            """
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        # 返回之前,我们修改这个对象的元数据让它等于原方法的元数据
        wrap.__name__ = func.__name__
        wrap.__doc__ = func.__doc__
        return wrap
    
    @add_cache
    def fibonacci(n):
        """
            This fibonacci
        """
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    print(fibonacci.__name__)
    print(fibonacci.__doc__)

    结果和我们设想的一样:

    fibonacci
    
            This fibonacci

    虽然是实现了我们的目的,但是这么做非常的不优雅,有没有比较优雅的做法呢。

    我们可以使用Python标准库functools下的update_wrapper来实现:

    from functools import update_wrapper
    
    def add_cache(func):
        """
            This add_cache
        """
        cache = {}
        def wrap(*args):
            """
                This wrap
            """
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        # 使用update_wrapper来进行替换
        update_wrapper(wrap, func, assigned=('__name__',), updated=('__dict__',))
        return wrap
    
    @add_cache
    def fibonacci(n):
        """
            This fibonacci
        """
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    print(fibonacci.__name__)
    print(fibonacci.__doc__)

    结果如下:

    fibonacci
    
                This wrap

    解析:

    update_wrapper:
        第一个参数:代表装饰方法
        第二个参数:代表被装饰方法
        assigned:代表那些属性是需要替换的,不写的就代表不替换。(可以省略不写assigned=)
        updated:代表哪些属性需要合并,因为原方法有一些属性,装饰方法也有一些属性,所以他们两个里面的内容,需要合并在一起。(同样可以省略不写updated=)
    
    需要注意的是呢,update_wrapper中的assignedupdated都有一个默认的参数,来看一下这个方法的源代码:
    WRAPPER_ASSIGNMENTS = ('__module__', '__name__', '__qualname__', '__doc__',
                           '__annotations__')
    WRAPPER_UPDATES = ('__dict__',)
    def update_wrapper(wrapper,
                       wrapped,
                       assigned = WRAPPER_ASSIGNMENTS,
                       updated = WRAPPER_UPDATES):
    所以,即使我们不指定后两个参数,也是可以实现我们的需求的。

    还有一种更方便的做法,Python为我们提供了一个装饰器:@wraps(),同样在functools下面,这个装饰器是用在装饰方法上面的,它接收三个参数,分别是被装饰方法,assigned和updated。当然,后两个参数都有默认值,同样是WRAPPER_ASSIGNMENTS和WRAPPER_UPDATES,所以我们可以这样:

    from functools import wraps
    
    def add_cache(func):
        """
            This add_cache
        """
        cache = {}
        # 使用装饰器来保存被装饰方法的元数据
        @wraps(func)
        def wrap(*args):
            """
                This wrap
            """
            if args not in cache:
                cache[args] = func(*args)
            return cache[args]
        return wrap
    
    @add_cache
    def fibonacci(n):
        """
            This fibonacci
        """
        if n <= 1:
            return 1
        return fibonacci(n - 1) + fibonacci(n - 2)
    
    print(fibonacci.__name__)
    print(fibonacci.__doc__)

    结果如下:

    fibonacci
    
            This fibonacci

    实际上在@wraps()这个装饰器内部,使用的就是update_wrapper()方法。

    END


    作者: 秋名山车神 
    链接:http://www.imooc.com/article/16248
    来源:慕课网

  • 相关阅读:
    CentOS6.0/RedHat Server 6.4安装配置过程 详细图解!
    关于Haproxy安装和配置:负载配置【haproxy.cfg】问题记录
    菜鸟学习Struts——bean标签库
    2013——2014总结
    高效程序员的45个习惯读书 ——敏捷开发修炼之道笔记之态度决定一切
    Hive深入浅出
    Java从入门到精通——调错篇之SVN 出现 Loced错误
    考试系统优化——准备工作
    深入解析:分布式系统的事务处理经典问题及模型(转载分享)
    黑客攻击 UVa11825
  • 原文地址:https://www.cnblogs.com/lgh344902118/p/6835536.html
Copyright © 2020-2023  润新知