选择性缓存/记忆的装饰器

Posted

技术标签:

【中文标题】选择性缓存/记忆的装饰器【英文标题】:Decorators for selective caching / memoization 【发布时间】:2015-04-03 08:53:47 【问题描述】:

我正在寻找一种构建装饰器@memoize 的方法,我可以在如下函数中使用它:

@memoize
my_function(a, b, c):
    # Do stuff 
    # result may not always be the same for fixed (a,b,c)
return result

那么,如果我这样做:

result1 = my_function(a=1,b=2,c=3)
# The function f runs (slow). We cache the result for later

result2 = my_function(a=1, b=2, c=3)
# The decorator reads the cache and returns the result (fast)

现在说我想强制更新缓存

result3 = my_function(a=1, b=2, c=3, force_update=True)
# The function runs *again* for values a, b, and c. 

result4 = my_function(a=1, b=2, c=3)
# We read the cache

在上面的结尾,我们总是有result4 = result3,但不一定是result4 = result,这就是为什么需要一个选项来为相同的输入参数强制更新缓存。

我该如何解决这个问题?

注意joblib

据我所知joblib 支持.call,这会强制重新运行,但它does not update the cache。

后续使用klepto

有没有办法让klepto(参见@Wally 的答案)默认将其结果缓存在特定位置? (例如/some/path/)并跨多个功能共享此位置?例如。我想说

cache_path = "/some/path/"

然后@memoize 同一路径下给定模块中的几个函数。

【问题讨论】:

functools.lru_cache 是一个 memoization 装饰器,它提供了一种清除整个缓存的方法(但不是像您的示例中那样的特定调用)。 【参考方案1】:

我建议查看joblibklepto。两者都有非常可配置的缓存算法,并且可以做你想做的。

两者都绝对可以为result1result2 进行缓存,并且klepto 提供对缓存的访问,因此可以pop 从本地内存缓存中获取结果(无需从存储的存档中删除它,在数据库中说)。

>>> import klepto
>>> from klepto import lru_cache as memoize
>>> from klepto.keymaps import hashmap
>>> hasher = hashmap(algorithm='md5')
>>> @memoize(keymap=hasher)
... def squared(x):
...   print("called")
...   return x**2
... 
>>> squared(1)
called
1
>>> squared(2)
called
4
>>> squared(3)
called
9
>>> squared(2)
4
>>> 
>>> cache = squared.__cache__()
>>> # delete the 'key' for x=2
>>> cache.pop(squared.key(2))
4
>>> squared(2)
called
4

不完全是您正在寻找的关键字界面,但它具有您正在寻找的功能。

【讨论】:

谢谢沃利。我已经使用了joblib,但找不到一种方法来满足我的要求(最接近的是.call,但正如我在笔记中提到的,它不能满足我的需求)。我会调查klepto 我看到了您的编辑。我相信klepto 可以满足您的需求,因为它为任何缓存提供了dict 接口,并且还为缓存提供了pop 方法——实际上,它提供了所有dict 方法。 这很有帮助。谢谢沃利。它看起来像是一个比 joblib 更完整的记忆库。很高兴知道。在此说明中,您是否碰巧知道如何使用 disk 持久性执行上述操作? 我相信klepto.archives.dir_archive 在磁盘上创建一个存档,每个文件一个条目,而klepto.archives.file_archive 在磁盘上创建一个存档,所有条目都在一个文件中。此外,您可以使用SQL 存档之一写入磁盘。 见***.com/a/21447994/4482921。我还记得关于 SO 的另一个更好的例子,但似乎无法立即找到链接。【参考方案2】:

你可以这样做:

import cPickle


def memoize(func):
    cache = 

    def decorator(*args, **kwargs):
        force_update = kwargs.pop('force_update', None)
        key = cPickle.dumps((args, kwargs))
        if force_update or key not in cache:
            res = func(*args, **kwargs)
            cache[key] = res
        else:
            res = cache[key]
        return res
    return decorator

装饰器接受额外的参数force_update(你不需要在你的函数中声明它)。它从kwargs 弹出。因此,如果您没有使用这些参数调用该函数,或者您传递的是 force_update = True,该函数将被调用:

@memoize
def f(a=0, b=0, c=0):
    import random
    return [a, b, c, random.randint(1, 10)]


>>> print f(a=1, b=2, c=3)
[1, 2, 3, 9]
>>> print f(a=1, b=2, c=3) # value will be taken from the cache
[1, 2, 3, 9]
>>> print f(a=1, b=2, c=3, force_update=True)
[1, 2, 3, 2]
>>> print f(a=1, b=2, c=3) # value will be taken from the cache as well
[1, 2, 3, 2]

【讨论】:

【参考方案3】:

如果你想自己做:

def memoize(func):
    cache = 
    def cacher(a, b, c, force_update=False):
        if force_update or (a, b, c) not in cache:
            cache[(a, b, c)] = func(a, b, c)
        return cache[(a, b, c)]
    return cacher

【讨论】:

【参考方案4】:

这纯粹是关于klepto的后续问题......

流动将扩展@Wally 的示例以指定目录:

>>> import klepto
>>> from klepto import lru_cache as memoize
>>> from klepto.keymaps import hashmap
>>> from klepto.archives import dir_archive
>>> hasher = hashmap(algorithm='md5')
>>> dir_cache = dir_archive('/tmp/some/path/squared')
>>> dir_cache2 = dir_archive('/tmp/some/path/tripled')
>>> @memoize(keymap=hasher, cache=dir_cache)
... def squared(x):
...   print("called")
...   return x**2
>>> 
>>> @memoize(keymap=hasher, cache=dir_cache2)
... def tripled(x):
...   print('called')
...   return 3*x
>>>

您也可以使用file_archive,将路径指定为:

cache = file_archive('/tmp/some/path/file.py') 

【讨论】:

太棒了。谢谢@迈克!我会阅读文档,但是您使用的特定键盘映射背后的假设是什么?即keymap=hashmap(algorithm='md5') 我不确定你所说的“假设”是什么意思......但也许这个链接会澄清:github.com/uqfoundation/klepto/blob/master/klepto/keymaps.py,并且可以在这里找到一组“编码”:github.com/uqfoundation/klepto/blob/master/klepto/crypto.py我使用了给定示例中使用的键映射。如果以上内容不能回答您的问题,请告诉我。

以上是关于选择性缓存/记忆的装饰器的主要内容,如果未能解决你的问题,请参考以下文章

记忆装饰器保持存储的值

为啥 Python 的装饰器语法比普通的包装器语法提供更快的记忆代码?

装饰器器应用及用途

基于云的记忆

Python 记忆/延迟查找属性装饰器

在 python memoization 装饰器类中设置 get/set 属性