传入 list 或 dict 会触发 typeerror 是因为 lru_cache 用参数元组作缓存键,而 list、dict 是可变对象,__hash__ 被设为 none,无法参与哈希计算,即使嵌套或为空也一样失败。

为什么传入 list 或 dict 会触发 TypeError: unhashable type
lru_cache 内部用参数元组作为缓存键,而 Python 字典和列表默认不可哈希——它们是可变对象,__hash__ 方法被显式设为 None。一旦你把 list、dict、set 这类对象直接传给被装饰的函数,lru_cache 就会在构造键时调用 hash((arg1, arg2, ...)),结果立即抛出异常。
- 错误示例:
@lru_cache() def process(config): ...; process({"mode": "prod"})→ 报错 - 根本原因不是“不支持字典”,而是“字典不能参与哈希计算”,哪怕只出现在嵌套结构里(如
[{"a": 1}])也会失败 - 注意:空字典
{}和空列表[]同样不可哈希,不存在“空就安全”的例外
如何让含字典/列表的参数变成可缓存
核心思路是把不可哈希的结构转成可哈希的等价表示,且保证语义一致、无歧义。
- 最常用:用
tuple替代list,用frozenset或排序后的tuple替代set;对dict,推荐tuple(sorted(d.items()))(要求 key 和 value 都可哈希) - 更通用:用
json.dumps(obj, sort_keys=True)转成字符串(注意浮点精度、NaN 处理、非 JSON 类型需预处理) - 避免踩坑:不要用
str(dict),因为键顺序在不同 Python 版本或运行中可能变化,导致同一字典生成不同键 - 若参数结构固定,可提前封装一层:比如把
config: dict改成config_key: str,由调用方负责生成稳定 key
typed=True 对可变参数没帮助,但会影响基础类型判断
typed 参数只控制是否把类型也纳入哈希(比如 1 和 1.0 是否算不同键),它完全不改变对可变对象的处理逻辑——dict 即使加了 typed=True 依然不可哈希,照样报错。
-
@lru_cache(typed=True) def f(x): ...; f([1])→ 仍报unhashable type - 但它会让
f(1)和f(1.0)分别缓存,而默认typed=False下二者共享缓存 - 开启
typed=True会略微增加内存开销和哈希计算时间,高频调用时建议实测影响
类方法中 self 导致的“伪可变参数”问题
看起来没传可变对象,但实例方法隐含 self 参数——而 self 是一个对象实例,默认按内存地址哈希。不同实例即使内容相同,self 的哈希值也不同,导致缓存无法复用,表现类似“参数总在变”。
- 现象:
cache_info().currsize持续上涨,命中率接近 0 - 错误写法:
class Loader: @lru_cache() def fetch(self, key): ... - 正确做法:提取为模块级函数,或改用
@cached_property(仅限属性访问场景) - 如果必须绑定实例状态,考虑把需要缓存的部分抽成纯函数,把
self中的稳定字段(如self.host、self.timeout)显式作为参数传入
float('inf')、NaN、自定义对象)产生不一致哈希,会导致静默缓存污染。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











