用weakref实现按需释放式LRU缓存,核心是解耦缓存存活与对象生命周期:WeakValueDictionary托管值(弱引用),双向链表维护访问顺序,finalize兜底释放外部资源,避免强引用阻碍GC。

用 weakref 实现“按需释放”式 LRU 缓存,核心不是替代 LRU 逻辑,而是解耦缓存存活与对象生命周期——让大资源在无人强引用时自动退出缓存,不靠淘汰策略“踢出”,而由 GC 主动回收。这特别适合模型实例、图像数据、数据库连接池等重量级对象。
用 WeakValueDictionary 承载缓存主体
普通字典会强持有值,导致即使资源已无业务使用,仍卡在缓存里。WeakValueDictionary 的值是弱引用,一旦外部所有强引用消失(比如用户关闭窗口、请求结束),对应缓存项自动失效,无需手动清理或定时扫描。
- 键建议用不可变类型(如 ID 字符串、元组),确保可哈希且稳定
- 不要把 WeakValueDictionary 当作“带过期的字典”——它不提供 TTL 或访问时间戳,只响应对象是否还活着
- 读取时需判断
cache.get(key)返回是否为None,因为对象可能已在后台被回收
用双向链表 + 弱引用节点管理 LRU 顺序
LRU 的“最近使用”排序不能依赖 WeakValueDictionary(它不记录访问顺序)。你需要额外维护一个轻量双向链表,节点中只存 key 和访问时间戳(或仅用于排序的序号),而**不存实际资源对象**——资源本身由 WeakValueDictionary 独立托管。
- 每次
get(key):先查 WeakValueDictionary 获取对象;若存在,更新链表中该 key 的位置;若为None,视为缓存未命中,走加载流程 - 每次
put(key, obj):先将obj存入 WeakValueDictionary;再将key插入链表头部;若缓存超限,从链表尾部弹出 key,并从 WeakValueDictionary 中尝试删除(但删不删得掉取决于对象是否还活着) - 链表节点本身极轻(仅字符串+指针),不构成内存压力,也不引入循环引用风险
配合 finalize 做资源释放兜底
有些大型资源(如 C 扩展句柄、临时文件)需要显式释放,不能只靠 Python 对象销毁。这时可在创建资源时注册 weakref.finalize:
-
weakref.finalize(obj, release_external_handle, handle_id)在obj被回收时触发清理 - 回调函数必须是纯函数式,不捕获
obj自身(否则形成强引用闭环) - 适合做日志记录、句柄关闭、内存池归还等确定性操作,比
__del__更可靠
避免常见陷阱
这类架构容易在细节上翻车:
- 不要对内置不可变类型(
str,int,tuple)尝试弱引用——它们默认不支持,weakref.ref("hello")会报错 - 不要在弱引用回调或 finalize 中反向强引用原对象(例如存到全局列表),否则阻止回收
- 测试时别依赖
gc.collect()立即生效——CPython 通常即时回收,但 PyPy 或某些嵌入场景可能延迟,应以行为(如后续 get 返回 None)为准,而非调用时机

















