functools.lru_cache不够用是因为它不支持手动控制缓存键生命周期、非哈希类型参数及命中/淘汰时的自定义逻辑;OrderedDict可精确实现LRU语义,提供move_to_end和popitem(last=False)等关键操作。

为什么直接用 functools.lru_cache 不够用?
当你需要手动控制缓存键的生命周期、支持非哈希类型参数、或想在缓存命中/淘汰时插入自定义逻辑(比如打日志、触发回调),functools.lru_cache 就不够灵活了。它底层虽也用类似有序结构,但封装过深,不暴露内部状态。
这时候用 collections.OrderedDict 自建更可控——它天然维护插入/访问顺序,move_to_end(key, last=True) 能把指定 key 移到末尾,popitem(last=False) 能按 FIFO 顺序弹出最老项,正好契合 LRU 的“最近最少使用”语义。
手写 LRU 类的关键三步
核心是模拟“访问即更新位置、容量超限即删头”的行为。注意不是所有操作都需重排,只在 get 和 set 时触发顺序调整:
-
get 时:若 key 存在,调用
move_to_end(key)把它挪到末尾;否则返回默认值或抛异常 -
set 时:先检查是否已存在——若存在,同样
move_to_end(key);若不存在且 size 已满,先popitem(last=False)删最老项,再插入新键值对 -
size 控制:用
len(ordered_dict)获取当前大小,和预设的maxsize比较;maxsize=None表示不限容,此时跳过淘汰逻辑
示例片段:
from collections import OrderedDict
<p>class LRUCache:
def <strong>init</strong>(self, maxsize=128):
self.maxsize = maxsize
self.cache = OrderedDict()</p><pre class="brush:php;toolbar:false;">def get(self, key):
if key not in self.cache:
return None
self.cache.move_to_end(key) # 提升为最近使用
return self.cache[key]
def put(self, key, value):
if key in self.cache:
self.cache.move_to_end(key)
elif self.maxsize is not None and len(self.cache) >= self.maxsize:
self.cache.popitem(last=False) # 删除最久未用
self.cache[key] = value
OrderedDict 在 Python 3.7+ 中还有必要吗?
Python 3.7+ 的普通 dict 也保证插入顺序,但它不提供 move_to_end 和 popitem(last=False) 这两个关键操作。你无法高效地把某个已有 key “提到末尾”,也不能 O(1) 时间删掉最老的那一个——只能遍历 keys() 找第一个,时间复杂度退化为 O(n)。
所以即使版本够新,只要你要做真正的 LRU(而非简单 FIFO 或靠重建 dict 模拟),OrderedDict 仍是不可替代的。别被“dict 有序”误导,顺序保持 ≠ 顺序可操作。
容易踩的坑:线程安全与 key 的可哈希性
OrderedDict 本身不是线程安全的。如果多个线程并发调用 get/put,可能因中间状态不一致导致 KeyError 或漏删项。真要多线程用,得加 threading.Lock 包裹所有读写操作。
另外,OrderedDict 的 key 仍需满足可哈希(hashable)要求——列表、字典等不可变类型不能当 key。如果你的缓存 key 是动态构造的复杂对象,得提前转成 tuple 或 frozenset,或者自己实现 __hash__ 和 __eq__。
最后提醒一句:别在 __init__ 里传 maxsize=0 以为能禁用缓存——这会导致每次 put 都立即淘汰,实际变成空缓存,但代码逻辑依然执行,容易掩盖问题。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











