不能直接用 dict 存过期数据,因其不感知时间、需手动检查导致易漏;定时器清理有并发和迭代异常风险;推荐惰性清理,访问时顺手剔除过期项,或辅以 heapq+单线程轮询。

为什么不能直接用 dict 存过期数据?
因为 dict 本身不感知时间,插入后就一直占内存,哪怕键值对逻辑上已过期。你得自己在读写时检查 timestamp 或 ttl,但手动做容易漏——比如只查了 get 没查 __contains__,或者遍历时没跳过过期项,结果拿到脏数据。
用 threading.Timer 定时清理?别这么干
定时器看似优雅,但实际问题一堆:多个键过期时间不同,一个 Timer 不够用;频繁创建销毁 Timer 开销大;程序退出时未 cancel 的 timer 可能抛异常;更麻烦的是,timer 回调里删字典元素会触发 RuntimeError: dictionary changed size during iteration。
- 真正轻量又安全的做法是「惰性清理」:每次访问时顺手踢掉过期项
- 如果真需要后台清理(比如内存敏感场景),改用
heapq维护最小堆存过期时间戳,再配一个单线程轮询,比一堆Timer稳定得多 -
time.time()返回 float,注意浮点精度误差,比较时建议用而非 <code>
一个可用的 ExpiringDict 实现要点
核心是把过期逻辑封装进 __getitem__、__contains__ 和 pop,避免用户调用时踩坑。示例关键片段:
class ExpiringDict:
def __init__(self, max_age_seconds: float):
self._data = {}
self._max_age = max_age_seconds
<pre class="brush:python;toolbar:false;">def __getitem__(self, key):
value, timestamp = self._data[key]
if time.time() - timestamp > self._max_age:
del self._data[key]
raise KeyError(key)
return value
def __setitem__(self, key, value):
self._data[key] = (value, time.time())
def get(self, key, default=None):
try:
return self[key]
except KeyError:
return default
- 存的是
(value, timestamp)元组,不是嵌套 dict,减少对象开销 - 不要重写
keys()或items()——它们不该自动过滤过期项,否则遍历时行为不一致;真要干净迭代,提供alive_items()方法更明确 - 如果需要线程安全,直接给
_data加threading.RLock,别只锁部分方法
用第三方库 expiringdict 还是自己写?
pip install expiringdict 后,ExpiringDict(max_len=100, max_age_seconds=60) 看似省事,但它默认不清理过期项,除非你显式调用 purge() 或设 auto_purge=True。而 auto_purge=True 是在每次操作前扫全表,O(n) 复杂度——当字典有 10 万键时,每次 get 都扫一遍,性能崩得很快。
- 小项目、键少、QPS 低,用它没问题
- 中高并发或大数据量,必须自己控制清理时机,要么惰性(推荐),要么按需批量 purge
- 注意它的
max_len是 LRU 驱逐,和过期无关,两个机制正交,别混淆
真正难的不是实现过期,而是决定「谁负责清理」和「什么时候清理」——这取决于你的访问模式和内存压力,没有银弹。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











