首页 > 代码库 > python Decorator

python Decorator

之前在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] = res13             return res14 15     return inner_deco16 17 @func_cache18 def add_two_number(a, b):19     return a + b20  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_cachedef add_two_number(a, b):    return a + b@func_cachedef product_two_number(a, b):    return a * bif __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 wrapsimport randomdef 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 * bif __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_cachedef add_two_number(a, b):    return a + b

decorator模块实现有参装饰器:

import randomfrom 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

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

  

 

python Decorator