4

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

例如,假设我有

@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
4

3 回答 3

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
于 2013-09-28T13:24:19.630 回答
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

于 2013-09-13T16:40:03.233 回答
1

你见过dogpile.cache吗?

这是一个缓存系统,正是这样做的。

您也许可以只使用dogpile。如果没有,您可以查看它的源代码以确切了解它是如何工作的。

顺便说一句,dogpile.cache 处理了您应该担心的所有小细节:

  • 保持钥匙分开
  • 序列化/反序列化
  • 价值到期和重新生效
  • 处理缓存命中和未命中
  • ETC
于 2013-09-13T16:52:39.347 回答