eq 和 hash 不配套会导致字典/集合失效,因哈希表需二者协同:先用 hash 定位桶,再用 eq 确认相等;若只重写 eq 而未定义 hash__,实例不可哈希;若 __hash 依赖可变字段或与 eq 字段不一致,则引发去重失败、查找错误等逻辑矛盾。

__eq__ 和 __hash__ 不配套会导致字典/集合失效
Python 的 dict 和 set 依赖哈希表实现,流程是:先调用 __hash__ 定位桶,再用 __eq__ 确认是否真相等。如果你只重写了 __eq__,Python 会自动把 __hash__ 设为 None(即禁用哈希),此时尝试把实例放进 set 或当 dict 的键,立刻报错:TypeError: unhashable type。
- 哪怕两个对象
a == b返回True,只要没定义__hash__,a in {b}就是False -
{a, b}会当成两个不同元素,而不是去重后的单个元素 - 这不是“功能没开”,而是 Python 主动阻止你写出逻辑矛盾的代码
为什么默认禁用哈希?因为可变性破坏哈希稳定性
Python 默认不给自定义类提供 __hash__,根本原因是:普通实例属性默认可变。一旦你在 __hash__ 里用了 self.x 这类字段,之后又执行了 obj.x = 100,哈希值就变了——但 dict 内部还记着旧桶位置,导致查不到、删不掉、甚至覆盖错键。
- 哈希值必须在对象生命周期内恒定;可变字段无法满足这点
- 即使你手动写
def __hash__(self): return hash(self.x),只要self.x可改,就踩坑 - 设
__hash__ = None是显式声明“我不适合当键”,比静默出错更安全
什么时候可以/必须定义 __hash__?看使用场景和不可变性
只有当你明确需要实例作为 dict 键或进 set,且能保证参与哈希的字段绝不修改时,才该定义 __hash__。否则,别硬上。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 推荐用
@dataclass(frozen=True):自动冻结所有字段,并生成安全的__eq__和__hash__ - 手动实现时,只对初始化后就不再变的字段计算哈希,例如:
return hash((self.id, self.name)) - 如果类本身需要支持状态变更(比如带缓存、计数器、开关标志),就放弃哈希化,改用
id(obj)或额外加一个uid字段做键 - 切忌在
__hash__中调用方法、读取 property、或访问外部状态——它们可能随时间变化
__eq__ 返回 True 但 hash 值不同,会直接破坏容器语义
这是最隐蔽也最危险的问题:你写了 __eq__,但没写 __hash__,Python 会回退到基于 id() 的默认哈希。结果就是两个逻辑相等的对象,哈希值不同,dict 把它们当完全不同的键处理。
- 现象:
Point(1, 2) == Point(1, 2)是True,但{Point(1, 2): 'a', Point(1, 2): 'b'}长度为 2 - 根源:默认
__hash__返回的是内存地址,而两个新构造的实例地址必然不同 - 修复不是“加个
__hash__”就行,必须确保它和__eq__用的字段完全一致、且不可变
真正难的不是写这两行方法,而是判断:这个类到底适不适合做值对象。一旦允许修改,哈希就不再是选项,而是陷阱。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










