python decorator参数使用函数的包装变量

时间:2013-09-13 15:41:37

标签: python python-decorators

我正在寻找一种方法来创建一个装饰器,以获得一个函数参数,该参数实际上使用传递给它包装的函数的变量。

例如,假设我有

@cache_decorator("my_key_{}".format(foo))
def my_function(foo, bar):
    pass

@cache_decorator("another_key_{}_{}".format(foo, bar)
def another_function(user, foo, bar):
    pass

目标是编写缓存包装器。装饰器将需要缓存键,但键将包含传递给函数的变量,并且对于它包装的每个函数都是不同的。

理想情况下,这允许装饰器检查给定键的缓存值,如果找不到它,则执行该函数以获取值并缓存它。这样,如果值在缓存中,则不执行创建值的代码(即my_function)。如果找不到它,它会执行my_function并将结果存储在缓存中并返回它。

另一种选择是类似于块:

def my_function(foo, bar):
    cache_value("my_key_{}".format(foo),{block of code to generate value that is only called if necessary})

在Objective-C或js中,这将是一个块,因此我可以将值生成保持在本地定义和可更改,但仅在必要时执行。我对python来说太新了,无法完全掌握如何通过它的闭包来实现这一点。

谢谢!

更新
虽然下面的解决方案适用于装饰器,但由于附加到每个缓存条目所需的额外元数据以确保它可以正确无效,因此我最终进入了类似块的路由。使用值生成定义此元数据(与缓存函数内部相对)更易于维护。这看起来像:

def my_function(foo, bar):
    def value_func():
        return code_to_generate_value_using_foo_bar

    return get_set_cache(key, value_func, ...)

def get_set_cache(key, value_function, ...):
    value = cache.get(key)
    if value is None:
        value = value_function()
        cache.set(key, value)
    return value

3 个答案:

答案 0 :(得分:3)

你可以让你的包装器获得一个关键的构建功能:

@cache_w_keyfunc(lambda foo, bar: (bar,))
def my_function(foo, bar):
    pass

@cache_w_keyfunc(lambda user, foo, bar: (foo, bar))
def another_function(user, foo, bar):
    pass

密钥生成器应返回可清除的内容,例如字符串元组。如果它们不可清除,例如列表,可能会将它们转换为字符串。

此密钥构建函数获取与函数本身相同的参数,并返回要使用的密钥。

def cache_w_keyfunc(keyfunc):
    def real_decorator(func):
        func.cache = {}
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # Create the key now out of the wrapped function's name and the given keys:
            key = (func.__name__, keyfunc(*args, **kwargs))
            try:
                return func.cache[cache_key]
            except KeyError:
                value = func(*args, **kwargs)
                func.cache.set(cache_key, value)
                return value
        return wrapper
    return real_decorator

答案 1 :(得分:1)

创建装饰器时可以传递两个列表。第一个包含位置参数的位置列表,第二个包含关键字参数的参数名称列表。

def cached(positions, names):
    def cached_decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            keys = [func.__name__] + [str(kwargs.get(name)) for name in sorted(names)] + [str(args[position]) for position in positions]
            cache_key = '_'.join(keys)
            cached_value = cache.get(cache_key)
            if cached_value:
                return cached_value
            value = func(*args, **kwargs)
            cache.set(cache_key, value)
            return cached_value
        return wrapper
    return cached_decorator

你会像这样使用它

# this will cache the function using b and name parameters
@cached([1], ["name"])
def heavy_calc(a, b, c, name=None):
    something_realy_slow() 
    return answer

问题是你还应该序列化函数的答案,并在从缓存中检索时反序列化。另一个问题是两个不同的调用函数可以给出相同的密钥(heavy_calc("hello_there", "foo")heavy_calc("hello", "there_foo"))。解决方法是使用json或msgpack创建args和kwargs的序列化,这样您就可以确保密钥是唯一的。

如果您使用的是Python 3.3并且不需要选择要缓存的参数,则可以使用functools.lru_cache

答案 2 :(得分:1)

你见过dogpile.cache吗?

这是一个完全正确的缓存系统。

你可能只能使用dogpile。如果没有,你可以查看它的来源,看看它是如何工作的。

顺便说一下,dogpile.cache会处理你应该担心的所有细节:

  • 保持密钥分开
  • 序列化/反序列化
  • 值到期和重新验证
  • 处理缓存命中和未命中