Ah, functools.lru_cache 的缓存失效有一些非常隐蔽的原因,不只是因为你显式调用了 cache_clear()。我来帮你梳理一下可能性,并给出 Python 实战层面的分析。
1️⃣ 可哈希性(Hashable)问题
lru_cache 的缓存是基于函数参数的哈希值来存储的:
from functools import lru_cache
@lru_cache(maxsize=128)
def foo(x):
return x * 2
隐蔽原因:如果参数是可变类型(如 list, dict),调用时就会报 TypeError: unhashable type,或者你为了避免报错做了转换(如 tuple(list_arg)),导致每次传入的 tuple 实例不同,缓存就失效了。
示例:
foo([1,2,3]) # TypeError
foo(tuple([1,2,3])) # 每次都可能生成新的 tuple,如果生成方式不同,缓存也不同
2️⃣ 函数重新定义
如果你的函数在模块中被多次导入或重新定义:
from functools import lru_cache
@lru_cache(maxsize=128)
def foo(x):
return x * 2
# 重新定义 foo
@lru_cache(maxsize=128)
def foo(x):
return x * 2
隐蔽原因:每次重新定义函数,相当于创建了新的函数对象,其缓存是空的。
场景:在 Jupyter Notebook 或热重载开发环境里,这很容易发生。
3️⃣ 默认参数可变
如果函数默认参数是可变对象:
@lru_cache(maxsize=128)
def foo(x, lst=[]):
lst.append(x)
return sum(lst)
隐蔽原因:默认参数对象每次可能被修改,导致缓存效果不可预测,甚至不同调用参数哈希值相同却产生不同结果。
4️⃣ 函数参数微小变化
浮点数误差:
foo(0.1 + 0.2) # 0.30000000000000004
foo(0.3)
这两个参数虽然看似相等,但哈希不同,缓存失效。
字符串编码:字符串前后有空格或编码差异,也会导致缓存失效。
5️⃣ LRU 超出容量
maxsize 设置太小,导致老的缓存条目被淘汰。
@lru_cache(maxsize=2)
def foo(x):
return x*2
foo(1)
foo(2)
foo(3) # 1 被踢掉
隐蔽原因:看似缓存应该命中,但因为最近访问策略(LRU)导致被清掉。
6️⃣ 跨线程或多进程使用
在多线程环境中,lru_cache 是线程安全的,但在多进程中每个进程都有自己的缓存。
隐蔽原因:在多进程场景中,可能你以为缓存命中,但实际上每个进程缓存独立,导致失效。
7️⃣ 外部可变对象影响
如果参数是对象的属性:
class A:
def __init__(self, val):
self.val = val
@lru_cache(maxsize=128)
def foo(a):
return a.val * 2
obj = A(1)
foo(obj) # 缓存
obj.val = 2
foo(obj) # 仍然命中缓存,返回 2 而不是 4
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70050070/viewspace-3117953/ ,如需转载,请注明出处,否则将追究法律责任。