我正在经历实现details Python 的 LRU 缓存装饰器并注意到这种行为,我觉得有点令人惊讶。当使用 staticmethod
或 classmethod
装饰器装饰时,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)
这怎么可能?对于 foo
和 foo1
实例,名为 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/