Python LRU缓存装饰器按实例分类

50

使用在此处找到的LRU Cache修饰符:http://code.activestate.com/recipes/578078-py26-and-py30-backport-of-python-33s-lru-cache/

from lru_cache import lru_cache
class Test:
    @lru_cache(maxsize=16)
    def cached_method(self, x):
         return x + 5

我能够使用这个方法创建一个带有装饰器的类方法,但是它最终会创建一个适用于所有 Test 类实例的全局缓存。然而,我的意图是创建一个每个实例都有自己的缓存。因此,如果我实例化3个 Tests,我将拥有3个LRU缓存,而不是适用于所有3个实例的1个LRU缓存。

唯一提示我知道发生了这种情况的是,在调用不同类实例的装饰方法的cache_info()时,它们都返回相同的缓存统计信息(考虑到它们正在与非常不同的参数进行交互,这种情况极不可能发生):

CacheInfo(hits=8379, misses=759, maxsize=128, currsize=128)
CacheInfo(hits=8379, misses=759, maxsize=128, currsize=128)
CacheInfo(hits=8379, misses=759, maxsize=128, currsize=128)

是否有装饰器或技巧可以让我轻松地使这个装饰器为每个类实例创建一个缓存?


2
记住,装饰器只是“def method: pass; method = decorate(method)”的语法糖。因此,您可以机械地将其翻译为在您的__init__中创建装饰方法。 - Francis Avila
你确定你知道“类方法”是用来干什么的吗?因为我认为你在寻找一个普通方法。如果一个类方法是实例特定的,那么它就是一个实例的普通方法,根据定义。或者你到底为什么需要一个类方法?或者你为什么想要一个“每个实例”的缓存? - Mayou36
3个回答

59

假设您不想修改代码(例如,因为您希望能够轻松移植到3.3并使用标准库functools.lru_cache,或者使用PyPI上的functools32而不是将配方复制粘贴到代码中),那么有一个明显的解决方案:对每个实例创建一个新的装饰实例方法。

class Test:
    def cached_method(self, x):
         return x + 5
    def __init__(self):
         self.cached_method = lru_cache(maxsize=16)(self.cached_method)

2
太棒了。但是如何针对只读 @property 进行此操作? - Gilly
@Gilly,你不需要这样做,只需创建一个“变量属性”,并将缓存值存储在那里即可。 - RBF06
在你的例子中,cached_method 既是一个变量名,也是一个方法名,在构造函数变量中,self.cached_method 变量存储了 cached_method 方法的值吗? - Ciasto piekarz
1
一个微妙的点是,这会创建一个 self -> lru_cache 实例 -> self 的引用循环。通常情况下这没问题,但值得注意的是,它从简单的引用计数清理转变为最终和可能被禁用的垃圾回收清理。 - mtraceur
1
注意:如果要在使用魔术方法(如__call__)时使用此答案,请参见此处。简而言之,对于使用__call__方法,答案需要更改为: self.__class__.__call__ = lru_cache(maxsize=16)(self.__class__.__call__) - vladkha

5
这样如何:一个函数装饰器,第一次在每个实例上调用时将该方法包装为lru_cache
def instance_method_lru_cache(*cache_args, **cache_kwargs):
    def cache_decorator(func):
        @wraps(func)
        def cache_factory(self, *args, **kwargs):
            print('creating cache')
            instance_cache = lru_cache(*cache_args, **cache_kwargs)(func)
            instance_cache = instance_cache.__get__(self, self.__class__)
            setattr(self, func.__name__, instance_cache)
            return instance_cache(*args, **kwargs)
        return cache_factory
    return cache_decorator

使用方法如下:

class Foo:
    @instance_method_lru_cache()
    def times_2(self, bar):
        return bar * 2

foo1 = Foo()
foo2 = Foo()

print(foo1.times_2(2))
# creating cache
# 4
foo1.times_2(2)
# 4

print(foo2.times_2(2))
# creating cache
# 4
foo2.times_2(2)
# 4

这里有一个 GitHub 上的代码片段,与一些内联文档有关。请查看这个链接

3
这些日子,methodtools会起作用。最初的回答。
from methodtools import lru_cache
class Test:
    @lru_cache(maxsize=16)
    def cached_method(self, x):
         return x + 5

你需要安装methodtools。原始答案翻译成"最初的回答"。
pip install methodtools

如果您仍在使用py2,则还需要使用functools32。"Original Answer"翻译成"最初的回答"。
pip install functools32

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接