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

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

  

原文地址:https://www.cnblogs.com/gcm688/p/5828661.html