因为functools.lru_cache仅适用于纯函数,缺乏动态调容、序列化、ttl等能力,而ordereddict天然支持lru操作且比手写双链表简单;但需注意线程安全、边界处理及性能取舍。

为什么不用 functools.lru_cache 而要自己实现?
因为 functools.lru_cache 是装饰器,只适合纯函数场景;它无法动态调整容量、不支持键值序列化/持久化、也不能在运行时清空特定项或统计命中率。如果你要嵌入到类实例中、配合数据库连接池、或做带 TTL 的混合缓存,就得手写底层逻辑。
OrderedDict 是最简可行方案的核心
OrderedDict 的 move_to_end(key) 和 popitem(last=False) 天然匹配 LRU 的“最近使用置尾、淘汰头节点”语义,比手动维护双链表+哈希表简单得多,且 Python 3.7+ 的普通 dict 虽保持插入顺序,但不支持 move_to_end —— 所以必须用 OrderedDict。
实操建议:
- 初始化时传入
maxsize,为None表示无限制 - 每次
get成功后调用self.cache.move_to_end(key) -
put前先检查是否已存在:存在则move_to_end;不存在且超容,则popitem(last=False) - 避免在多线程下直接使用——
OrderedDict非线程安全,加threading.Lock或改用collections.deque+dict组合
手写双链表+哈希表的性能取舍点
纯 OrderedDict 实现平均时间复杂度是 O(1),但底层仍涉及链表节点移动和字典查找的组合开销;真正高频(如每秒万级读写)、内存敏感(如嵌入式设备)场景,才值得上手写双向链表。
关键细节:
- 每个节点需同时存
key和value,否则淘汰时无法反查 key 清理哈希表 - 哈希表映射的是
key → node,不是key → value - 插入/访问都要更新链表位置:删节点 → 插到尾部,不是简单交换指针
- Python 中对象引用计数会让节点“悬空”更难察觉,务必显式设
node.prev = node.next = None再del node
容易被忽略的边界情况
多数实现会漏掉这三点:
-
put(key, None):允许存None值,不能用if self.cache[key]判是否存在,得用key in self.cache - 容量为 0 时,
put应直接丢弃,get永远返回None—— 不少代码把maxsize == 0当作无限处理 -
get未命中时不触发任何链表操作,但有些实现误把move_to_end放在 finally 块里,导致 KeyError 后还调用,抛KeyError两次
真实项目里,LRU 的“高效”不单看 O(1),更取决于你是否提前堵住了这些松动的缝隙。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











