Python functools避免把可变参数放进缓存键的实现方法
我在给一个按筛选条件生成报告的函数加 functools.lru_cache 时,最先遇到的不是命中率问题,而是调用方习惯传入 list 和 dict。这类对象内容可以改变,不能直接作为字典键。稳妥做法是:在缓存入口外保留自然的可变参数,在内部先递归规范化为稳定的 tuple,再交给缓存函数。
官方地址:https://docs.python.org/3/library/functools.html
@lru_cache的参数必须可哈希,list、dict、set 不能直接进入缓存键。- 规范化必须保留业务语义:字典按键排序,集合不能误当成有序列表。
- 数据源或配置变化时用
cache_clear()失效;缓存返回值也不要暴露可被调用方修改的共享对象。
先把“可变参数”与“缓存身份”分开
lru_cache 用函数参数组成缓存键,而底层缓存需要可哈希对象。直接写成 @lru_cache 后接收列表,会在真正查缓存前抛出 TypeError: unhashable type: 'list';把字典直接传入也有同样问题。更隐蔽的情况是:把列表临时转成元组,却没有明确列表顺序是否影响结果,可能把两个本应相同的查询拆成不同条目。
先问清楚一个边界:哪些字段决定结果?例如报表筛选器中的 region、status 和排序字段通常决定结果,页面展示用的备注不应该混进缓存键。

用递归规范化承接 list、dict 和 set
一个可复用的入口可以把嵌套容器转换成不可变结构。字典要按键排序后再转成键值对元组;集合本身没有稳定顺序,只有在业务上确实“只关心成员、不关心顺序”时才适合排序。
from functools import lru_cache
def freeze(value):
# 递归消除可变容器,确保缓存键只由稳定对象组成。
if isinstance(value, dict):
# 排序让同一组键值不因输入顺序不同而产生两个缓存条目。
return tuple(sorted((key, freeze(item)) for key, item in value.items()))
if isinstance(value, (list, tuple)):
# 列表顺序属于结果语义时,按原顺序保留。
return tuple(freeze(item) for item in value)
if isinstance(value, set):
# 集合无序;元素必须可比较,否则应改用业务定义的稳定排序键。
return tuple(sorted(freeze(item) for item in value))
return value
@lru_cache(maxsize=256)
def _build_report(filters_key):
# 内部函数只接收规范化键,避免 lru_cache 处理 list 或 dict。
return {"filters": filters_key, "rows": ("cached",)}
def build_report(filters):
# 对外接口仍接收调用方熟悉的字典。
return _build_report(freeze(filters))
这里的关键不是“所有容器都转成 tuple”,而是先定义语义。订单号列表若顺序代表优先级,就不能排序;标签集合若只代表成员关系,排序才合理。字典中的键也要本身可比较、可哈希,否则需要为业务对象提供明确的稳定标识。
命中之后还要设计失效和返回值边界
缓存命中只说明“同一个键已有结果”,不说明外部数据永远没变。官方接口提供 cache_info() 查看 hits、misses、maxsize 和 currsize,也提供 cache_clear() 主动清空。配置刷新、字典表切换或租户数据版本变更时,应把清理动作放在同一个更新流程中,而不是等用户偶然遇到旧结果。
另一个容易漏掉的边界是返回值。lru_cache 会保留返回值引用;如果返回一个可变字典,调用方改动它,后续命中可能读到被改过的对象。更安全的选择是返回只读结构、元组,或在对外层复制一份。
| 对象或动作 | 适合做法 | 需要注意 |
|---|---|---|
| list | 按业务顺序递归转 tuple | 不要为去重而擅自排序 |
| dict | 排序后的键值对 tuple | 键和值都要可稳定规范化 |
| set | 仅在无序语义明确时排序 | 自定义对象需提供稳定排序规则 |
| 数据变化 | 调用 cache_clear() | 与刷新动作绑定,避免旧结果残留 |

我会用这张清单做最后判断
- 缓存函数是否纯粹:不要缓存写数据库、发请求等副作用函数。
- 规范化是否符合业务:顺序、大小写、缺省值和集合语义不能凭感觉改变。
- 容量是否可控:长驻进程优先设置有限的
maxsize,再用cache_info()观察。 - 失效是否可达:数据源更新、配置切换或租户变更时,是否能触发清理。
如果一个参数很难定义稳定身份,或者结果依赖当前时间、随机数和外部状态,我通常会放弃 lru_cache,改用带版本号的显式缓存层。缓存键设计的目标不是把所有输入都“冻结”,而是让相同业务结果拥有相同身份,让不同结果不会误用旧数据。
常见问题
把 list 转成 tuple 后就一定安全吗?
不一定。还要确认列表顺序是否有业务意义,以及嵌套元素是否全部可哈希;如果结果依赖外部数据,还要补上失效策略。
为什么两个相同字典可能产生不同缓存条目?
如果直接把键值对按输入遍历顺序拼接,顺序不同就可能形成不同键。规范化时按键排序,可以把同一组字典内容收敛到同一个键。
lru_cache 线程安全后还需要自己加锁吗?
它能保护内部缓存结构的一致性,但并不保证并发 miss 时底层函数只执行一次;若函数有副作用,应该改掉缓存设计或在业务层增加去重控制。
Go filepath规范化用户传入路径的处理方法
- 上一篇
- Go filepath规范化用户传入路径的处理方法
- 下一篇
- Go sync.WaitGroup在不同阶段安全复用 WaitGroup的约束
-
- 文章 · python教程 | 2小时前 |
- Python typing用 TypeGuard 缩小联合类型的实现方法
- 290浏览 收藏
-
- 文章 · python教程 | 3小时前 | python ·
- Python dataclass用 slots 控制实例字段开销的实现方法
- 118浏览 收藏
-
- 文章 · python教程 | 5小时前 |
- Python sqlite3用 detect_types 转换日期字段的实现方法
- 159浏览 收藏
-
- 文章 · python教程 | 4天前 |
- Python json解析金额 JSON 保留 Decimal的实现方法
- 337浏览 收藏
-
- 文章 · python教程 | 4天前 |
- Python csv为不同分隔符注册 Dialect的实现方法
- 421浏览 收藏
-
- 文章 · python教程 | 4天前 | python ·
- Python tempfile让临时文件跨平台可删除的实现方法
- 274浏览 收藏
-
- 文章 · python教程 | 4天前 | python · pathlib ·
- Python pathlib安全计算相对路径的实现方法
- 199浏览 收藏
-
- 文章 · python教程 | 4天前 |
- Python asyncio取消任务后等待清理完成的实现方法
- 168浏览 收藏
-
- 文章 · python教程 | 4天前 |
- Python functools.cache 递归函数如何避免缓存错误结果
- 377浏览 收藏
-
- 文章 · python教程 | 4天前 | python · Python http.Client HTTPResponse HTTPConnection
- Python http.client 读取响应后为何必须关闭连接
- 116浏览 收藏
-
- 文章 · python教程 | 4天前 | 文件处理 · 安全 · python · Python zipfile ZipFile.extractall 危险路径
- Python zipfile ZipFile.extractall 如何检查危险路径
- 193浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 122次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 196次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 140次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 114次使用
-
- CMMLU
- 深入了解CMMLU中文评估基准,涵盖67个学科主题,提供数据集下载、Zero-shot/Five-shot评估方法及排行榜,助力优化中文语言模型性能。
- 99次使用
-
- go zero微服务实战性能优化极致秒杀
- 2022-12-27 207浏览
-
- Go pprof 排查慢接口:别只会看火焰图,先把问题问对
- 2026-06-01 101浏览
-
- Go JSON v2 实战:别急着替换 encoding/json,先搞懂这些变化
- 2026-06-01 437浏览
-
- Go 1.25 容器感知 GOMAXPROCS:K8s 里别再让 CPU limit 偷偷拖垮 P99
- 2026-06-01 473浏览
-
- Go weak.Pointer 实战:缓存别越跑越胖,先搞懂弱引用和 AddCleanup
- 2026-06-01 134浏览

