字典键必须可哈希,因为其底层哈希表依赖键的哈希值恒定不变来定位存储槽位;列表因可变且__hash__被设为none而不可哈希,元组仅当所有元素均不可变时才可哈希。

dict 的键必须可哈希,而可哈希的前提是对象在生命周期内哈希值恒定不变。元组满足这个前提——只要它所有元素都不可变,hash() 就能稳定返回一个整数;列表不行,因为它的 __hash__ 被显式设为 None,一用就报 TypeError: unhashable type: 'list'。
为什么字典底层不允许键的哈希值变化
字典用哈希表实现:插入时靠 hash(key) 算出槽位索引,查找时再算一次哈希去对应位置找。如果键中途变了(比如列表 append()),哈希值就不同了,原来存进去的位置就“找不到”了——不是查不到,是根本去错了地方。
- 这不是运行时检测出来的错误,而是设计上直接禁止:Python 在创建字典时就要求键必须可哈希,否则立刻拒绝
-
hash((1, 2))每次调用都返回相同整数;hash([1, 2])不是算不出来,而是语言层面抛出TypeError - 哪怕只改列表一个元素(如
l[0] = 99),id(l)不变,但哈希一致性已破坏,字典内部结构就失效了
元组看似安全,但嵌套可变对象照样报错
元组自身的不可变性 ≠ 元素递归不可变。只要里面塞了一个 list、dict 或 set,整个元组就不可哈希。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
-
(1, 2)✅ 可当键;(1, [2])❌ 报TypeError: unhashable type: 'list' -
hash((1, tuple([2])))✅ 行得通,因为tuple([2])把列表转成了不可变元组 - 动态构造时容易漏检:
tuple(user_input.split())若输入含空字符串或嵌套结构,可能产出合法但语义错误的键
单元素元组写法错误导致键类型误判
括号不决定类型,逗号才决定。没逗号就是普通值,不是元组。
-
(1)是int,type((1))返回<class></class>;真正单元素元组必须写成(1,) - 用
(1)当字典键不会报错(因为int可哈希),但会掩盖意图,后续加第二个元素时极易出错 - 函数参数解包、缓存装饰器里常出现这种写法:
cache[args]中args是元组,但若*args只传一个值,args就是(x,),不是(x)
t = (1, [2, 3]); t[1].append(4)。元组本身没变,t 还是那个 t,但哈希前提已经崩了——这种问题在线上多线程环境里往往表现为缓存错乱或数据丢失,而不是直接报错。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










