zoukankan      html  css  js  c++  java
  • python 装饰器

    之前在http://python.jobbole.com/86068/,看到关于装饰器的一些知识。

    1. 函数式装饰器:

      Decorator是一个函数,它以一个函数对象A为参数,返回另一个函数对象B。对象B定义在Decorator体内,形成一个闭包。函数A和函数B接受的参数相同。每当程序调用函数A时,实际上会转换为对函数B的调用

     1 def func_cache(func):
     2     cache = {}
     3     def inner_deco(*args):
     4         if args in cache:
     5             print('func {} is already cached with arguments {}'.format(
     6                 func.__name__, args))
     7             return cache[args]
     8         else:
     9             print('func {} is not cached with arguments {}'.format(
    10                 func.__name__, args)) 
    11             res = func(*args)
    12             cache[args] = res
    13             return res
    14 
    15     return inner_deco
    16 
    17 @func_cache
    18 def add_two_number(a, b):
    19     return a + b
    20  
    21 if __name__ == "__main__":
    22     print('1. add_two_number(1, 2)')
    23     add_two_number(1, 2)
    24     print('2. add_two_number(2, 3)')
    25     add_two_number(2, 3)
    26     print('3. add_two_number(1, 2)')
    27     add_two_number(1, 2)

    func_cache就是我们实现的Decorator,它以一个函数对象(func)作为参数,返回另一个函数对象(inner_deco),因此,当我们每次调用被func_cache装饰过的函数(add_two_number)时,调用的其实是inner_deco,也即:

        add_two_number(1, 2) --> inner_deco(1, 2)

    而inner_deco,它内部实现的就是函数返回值缓存的逻辑,并打印了一些调试信息

    但是这里有一个明显的问题:inner_deco只能接受*arg,也就是列表参数,这就限制了这个Decorator的使用范围。下面这个版本就添加了**kwargs的支持。需要注意的是,kwargs不能进行hash,也就不能直接作为python中字典的key值,因此这里现将其转成一个frozenset(frozenset是冻结的集合,它是不可变的,存在哈希值,好处是它可以作为字典的key,也可以作为其它集合的元素。缺点是一旦创建便不能更改,没有add,remove方法)

    # -*- coding: utf-8 -*-
    def func_cache(func):
        cache = {}
        def inner_deco(*args, **kwargs):
            key = (args, frozenset(kwargs.items()))
            print key
            if key not in cache:
                print "func {0} is not cached with arguments {1}, {2}".format(func.__name__, args, kwargs)
                res = func(*args, **kwargs)
                cache[key] = res
                return cache[key]
            else:
                print "func {0} is alread cached with arguments {1}, {2}".format(func.__name__, args, kwargs)
                return cache[key]
        return inner_deco
    
    @func_cache
    def add_two_number(a, b):
        return a + b
    
    @func_cache
    def product_two_number(a, b):
        return a * b
    
    if __name__ == "__main__":
       print('add_two_number func name is {}'.format(add_two_number.__name__)) add_two_number(
    1, 2) add_two_number(1, b=3) add_two_number(1, 2) product_two_number(2, 3) product_two_number(2, 3)

    新增加了一个product_two_number函数,用于测试func_cache中的字典cache是否对于每个被装饰的函数都分配了一个,即不同函数调用是否都会调用func_cache(func)

    这里的Decorator还有一个问题,它改变了被装饰函数add_two_number的签名,比如:

    print('add_two_number func name is {}'.format(add_two_number.__name__))

    # 输出 add_two_number func name is inner_deco

    这不是我们想要的,而且在复杂项目中,对于Bug的追踪也将是灾难性的。

    好在Python为我们提供了functools模块,其中的wraps装饰器可以帮助我们解决这个问题。

    # -*- coding: utf-8 -*-
    from functools import wraps
     
    def func_cache(func):
        cache = {}
        @wraps(func)
        def inner_deco(*args, **kwargs):
            key = (args, frozenset(kwargs.items()))
            if key not in cache:
                print('func {} is not cached with arguments {} {}'.format(
                    func.__name__, args, kwargs)) 
                res = func(*args, **kwargs)
                cache[key] = res
            return cache[key]
        return inner_deco

    带参数Decorator

    目前我们实现的函数缓存装饰器,会缓存所有遇到的函数返回值。我们希望能够对缓存数量上限做一个限制,从而在内存消耗和运行效率上取得折中。但是同时,对于不同的函数,我们希望做到缓存上限不同,例如对于运行一次比较耗时的函数,我们希望缓存上限大一些;反之,则小一些。这时,需要用到带参数的Decorator

    # -*- coding: utf-8 -*-
    from functools import wraps
    import random
    
    def outer_deso(size=10):
        def func_cache(func):
            cache = {}
            @wraps(func)
            def inner_deco(*args, **kwargs):
                key = (args, frozenset(kwargs.items()))
                print key
                if key not in cache:
                    print('func {} is not cached with arguments {} {}'.format(func.__name__, args, kwargs))
                    res = func(*args, **kwargs)
                    if len(cache) >= size:
                        luck_key = random.choice(list(cache.keys()))
                        print('func {} cache pop {}'.format(func.__name__, luck_key))
                        cache.pop(luck_key, None)
                    cache[key] = res
                    return cache[key]
                else:
                    print "func {0} is alread cached with arguments {1}, {2}".format(func.__name__, args, kwargs)
                    return cache[key]
            return inner_deco
        return func_cache
    
    
    
    @outer_deso(size=10)
    def add_two_number(a, b):
        return a + b
    
    
    @outer_deso(size=10)
    def product_two_number(a, b):
        return a * b
    
    
    if __name__ == "__main__":
        print('add_two_number func name is {}'.format(add_two_number.__name__))
        add_two_number(1, 2)
        add_two_number(1, b=3)
        add_two_number(1, 2)
        product_two_number(2, 3)
        product_two_number(2, 3)

    无参数的装饰器,@符号后面接的是一个可做Decorator的函数对象;而有参数的装饰器,@符号后面接的是一个函数调用,此函数调用返回的是一个可做Decorator的函数对象

    但是,从上面的代码中也可以看出,到了带参数的Decorator这一步,Decorator的实现已经有了两层的函数嵌套,难于理解且不够优雅。这就需要引用decorator模块。这个模块可以不仅可以减少实现Decorator过程中的函数嵌套,还可以完美的保持函数签名不被更改。

    首先实现最简单的无参数Decorator:  

    def func_cache(func):
        func._cache = {}
        func._cache_size = 3
        return decorate(func, _cache)
    
    def _cache(func, *args, **kwargs):
        key = (args, frozenset(func.__name__))
        if key not in func._cache:
            print "func {} not in cache".format(func.__name__)
            res = func(*args, **kwargs)
            if len(func._cache)>=func._cache_size:
                lucky_key = random.choice(list(func._cache.keys()))
                func._cache.pop(lucky_key, None)
                print "func {} pop cache key {}".format(func.__name__, lucky_key)
            func._cache[key] = res
    
        return func._cache[key]
    
    @func_cache
    def add_two_number(a, b):
        return a + b

    decorator模块实现有参装饰器:

    import random
    from decorator import decorate
     
    def func_cache(size=10):
        def wrapped_cache(func):
            func._cache = {}
            func._cache_size = size
            return decorate(func, _cache)
        return wrapped_cache
     
    def _cache(func, *args, **kwargs):
        key = (args, frozenset(kwargs.items()))
        if key not in func._cache:
            print('func {} not hit cache'.format(func.__name__))
            res = func(*args, **kwargs)
            if len(func._cache) >= func._cache_size:
                lucky_key = random.choice(list(func._cache.keys()))
                func._cache.pop(lucky_key, None)
                print('func {} pop cache key {}'.format(func.__name__, lucky_key))
            func._cache[key] = res
        return func._cache[key]
     
    @func_cache(size=3)
    def add_two_number(a, b):
        return a + b

    实现带参数的装饰器的方式是相同的:在之前不带参数的装饰器外面再包一层函数,通过闭包将参数绑定到装饰器上,并将装饰器返回。

      

  • 相关阅读:
    Example of Formalising a Grammar for use with Lex & Yacc
    TCL脚本语言基础介绍
    linux环境下的c++编程
    如何利用FPGA进行时序分析设计
    可移植的配置visual studio工程第三方库
    [转]windows10 1703 鼠标右键打开命令提示符cmd
    重载和const形参的学习心得
    华为codecraft2018总结
    【转】C/C++使用心得:enum与int的相互转换
    C++学习笔记1-使用数组进行vector初始化
  • 原文地址:https://www.cnblogs.com/gcm688/p/5828661.html
Copyright © 2011-2022 走看看