不行,functools.lru_cache不支持动态过期,因其仅按访问频次和容量淘汰,硬加时间判断会破坏一致性;应手写TTLCache类,用字典存(value, expire_time)元组,get前检查时间戳,并用threading.Lock保障线程安全。

用 functools.lru_cache 加时间戳判断是否过期?不行,它不支持动态失效
lru_cache 本身没有过期逻辑,只按调用频次和最大容量淘汰。硬塞时间判断(比如在装饰函数里查 time.time())会破坏缓存一致性——同一个键可能多次计算,且无法自动清理过期项。
真正可控的方式是自己管理字典 + 时间戳。核心思路:缓存项存为 {key: (value, expire_time)},每次 get 前检查 expire_time 是否已过。
- 别用
datetime.now()做比较,用time.time()更轻量、无时区干扰 - 过期时间建议统一用「绝对时间戳」(如
time.time() + 60),避免每次取值都重新算相对时间 - 读写非原子,多线程下需加
threading.Lock,否则可能读到半更新状态
手写一个线程安全的带 TTL 缓存类
下面是一个最小可用实现,支持 set、get、delete 和自动过期清理:
import time
import threading
class TTLCache:
def __init__(self):
self._cache = {}
self._lock = threading.Lock()
def set(self, key, value, ttl_seconds):
with self._lock:
self._cache[key] = (value, time.time() + ttl_seconds)
def get(self, key, default=None):
with self._lock:
item = self._cache.get(key)
if item is None:
return default
value, expire_at = item
if time.time() > expire_at:
del self._cache[key]
return default
return value
def delete(self, key):
with self._lock:
self._cache.pop(key, None)
注意:ttl_seconds 是从调用 set 开始计时的相对秒数,不是固定时刻。如果需要“固定过期点”,传入 expire_at = time.time() + ttl 即可,但语义不如相对 TTL 清晰。
立即学习“Python免费学习笔记(深入)”;
为什么不用 redis-py 或 diskcache?本地内存场景下太重
如果你只是想避免重复调用某个耗时函数(比如解析配置文件、查本地 JSON),引入 Redis 就是杀鸡用牛刀;diskcache 虽支持 TTL,但默认落盘,IO 开销和序列化成本在纯内存场景下不必要。
-
diskcache.Cache的expire参数只对set生效,且后台线程定期扫描过期项,延迟不可控 - 纯内存字典 +
time.time()判断,零依赖、毫秒级响应、无 GC 压力(只要 key 不爆炸) - 若需自动后台清理僵尸项(比如长期不访问但已过期的 key),得额外起个
threading.Timer或用weakref配合回调——但大多数本地缓存根本不需要,访问时惰性清理更简单可靠
常见误用:把缓存键设成可变对象或未哈希对象
Python 字典要求 key 可哈希。list、dict、set 直接当 key 会报 TypeError: unhashable type。
- 函数参数含
dict时,别直接用args + kwargs.items()当 key,先json.dumps(sorted(kwargs.items()))或用hashlib.md5()摘要 - 自定义类实例作 key?确保实现了
__hash__和__eq__,且属性不变——否则缓存会失效或冲突 - 字符串 key 注意编码一致性,比如混用
'user:123'和b'user:123'
过期机制本身很简单,难的是键设计和并发边界。一旦 key 不稳定或锁粒度不对,缓存就变成定时炸弹。


















