直接回答:lru_cache 是一个装饰器,内部用一个 dict 存「参数元组 → 返回值」,再用一条双向链表维护访问顺序;命中时把节点移到链表头部,缓存满(达到 maxsize)时淘汰链表尾部即最久未使用的条目。整个结构由一把锁保护,多线程下并发调用是安全的,同一个 key 的重复计算在锁内去重。
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
return n if n < 2 else fib(n - 1) + fib(n - 2)
fib.cache_info() # hits/misses/maxsize/currsize
fib.cache_clear()
展开解析,坑主要有五个。一是所有参数必须可哈希,传 list/dict 会直接 TypeError。二是缓存强引用返回值,maxsize=None 时无界增长,长生命周期进程里可能内存膨胀。三是装饰实例方法时 self 也进 key,缓存持有实例引用,导致对象永远无法被 GC 回收——这种情况应改为在实例属性上缓存、用 weakref,或者缓存以不可变 id 为参数的模块级函数。四是命中时不执行函数体,副作用只发生一次,不适合"顺带记日志、写库"的函数。五是默认 1 和 1.0、True 视为同一 key(哈希与相等性一致),需要区分时用 typed=True。
实践要点:纯函数、参数空间小、重复调用多的场景收益最大;缓存外部数据的函数要想清楚失效策略,lru_cache 本身没有 TTL,只能靠 cache_clear() 整体清空。
追问方向:如何实现一个带 TTL 的缓存装饰器?递归配合 lru_cache 为什么能把指数复杂度降到多项式?
(约 520 字)