当使用 staticmethod 或 classmethod 装饰器装饰时,类中的 Python LRU 缓存忽略 maxsize 限制

标签 python python-3.x caching lru

我正在经历实现details Python 的 LRU 缓存装饰器并注意到这种行为,我觉得有点令人惊讶。当使用 staticmethodclassmethod 装饰器装饰时,lru_cache 会忽略 maxsize 限制。考虑这个例子:

# src.py

import time
from functools import lru_cache


class Foo:
    @staticmethod
    @lru_cache(3)
    def bar(x):
        time.sleep(3)
        return x + 5


def main():
    foo = Foo()
    print(foo.bar(10))
    print(foo.bar(10))
    print(foo.bar(10))

    foo1 = Foo()
    print(foo1.bar(10))
    print(foo1.bar(10))
    print(foo1.bar(10))

if __name__ == "__main__":
    main()

从实现中,我很清楚以这种方式使用 LRU 缓存装饰器将为类 Foo 的所有实例创建一个共享缓存。然而,当我运行代码时,它一开始等待 3 秒,然后打印出 15 六次,中间没有暂停。

$ python src.py 
# Waits for three seconds and then prints out 15 six times
15
15
15
15
15
15

我期待着它——

  • 等待 3 秒钟。
  • 然后打印 15 三次。
  • 然后再次等待 3 秒钟。
  • 最后,打印 15 三次。

使用实例方法运行上述代码的行为方式与我在要点中解释的方式相同。

使用缓存信息检查 foo.bar 方法给出以下结果:

print(f"{foo.bar.cache_info()=}")
print(f"{foo1.bar.cache_info()=}")
foo.bar.cache_info()=CacheInfo(hits=5, misses=1, maxsize=3, currsize=1)
foo1.bar.cache_info()=CacheInfo(hits=5, misses=1, maxsize=3, currsize=1)

这怎么可能?对于 foofoo1 实例,名为 tuple 的缓存信息是相同的(这是预期的),但是 LRU 缓存的行为为何就像它被应用为 一样lru_cache(无)(func)。这是因为描述符干预还是其他原因?为什么它忽略缓存限制?为什么使用实例方法运行代码会像上面解释的那样工作?

编辑: 正如 Klaus 在评论中提到的那样,这是缓存 3 个 key ,而不是 3 个访问。因此,要驱逐某个键,需要使用不同的参数调用该方法至少 4 次。这就解释了为什么它会快速打印 15 六次,中间不会暂停。这并不完全忽视最大限制。

此外,在实例方法的情况下,lru_cache 利用 self 参数来散列并为缓存字典中的每个参数构建 key 。因此,由于哈希计算中包含 self,因此每个新实例方法对于相同参数都将具有不同的键。对于静态方法,没有 self 参数,对于类方法,cls 是不同实例中的同一个类。这解释了他们的行为差异。

最佳答案

正如您在编辑中所说,@staticmethod@classmethod 装饰器将使缓存在所有实例之间共享。

import time
from functools import lru_cache


class Foo:
    @staticmethod
    @lru_cache(3)
    def foo(x):
        time.sleep(1)
        return x + 5


class Bar:
    @lru_cache(3)
    def bar(self, x):
        time.sleep(1)
        return x + 5

def main():
    # fill the shared cache takes around 3 seconds
    foo0 = Foo()
    print(foo0.foo(10), foo0.foo(11), foo0.foo(12))

    # get from the shared cache takes very little time
    foo1 = Foo()
    print(foo1.foo(10), foo1.foo(11), foo1.foo(12))

    # fill the instance 0 cache takes around 3 seconds
    bar0 = Bar()
    print(bar0.bar(10), bar0.bar(11), bar0.bar(12))

    # fill the instance 1 cache takes around 3 seconds again 
    bar1 = Bar()
    print(bar1.bar(10), bar1.bar(11), bar1.bar(12))

if __name__ == "__main__":
    main()

关于当使用 staticmethod 或 classmethod 装饰器装饰时,类中的 Python LRU 缓存忽略 maxsize 限制,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70409673/

相关文章:

javascript - PhoneGap 禁用缓存

java - 无法从不同节点启动geode服务器

python - 使 Django 中的 View 缓存过期?

python - 更改运行进程的输出重定向

python - 基于系列条件创建新的 pandas 列

python - Python 中的线程/多处理

python - 递归中的返回语句

python - 无法将 'bytes' 对象隐式转换为 str

python-3.x - 如何在Python中使用列表数据时绘制3D图形

python-3.x - 使用列表从字典中消除值