仅重载__eq__不足以让自定义对象在字典/集合中正确工作;必须同时实现__hash__,且相等对象的哈希值必须一致,否则因默认哈希基于id导致逻辑相等但容器中被视为不同元素。

直接说结论:仅重载 __eq__ 不足以让两个自定义对象在所有场景下“真正相等”;若你希望它们能作为字典键、存入集合,或参与哈希相关操作,必须同时合理实现 __hash__,且保证相等对象的哈希值一致。
为什么只改 __eq__ 会导致字典/集合失效
Python 中,dict 和 set 依赖哈希快速定位元素。当你把一个自定义对象放进 set 或作为 dict 的键时,Python 会先调用它的 __hash__ 方法得到桶位置;只有哈希值相同,才会进一步调用 __eq__ 判断是否真相等。
如果你只重写了 __eq__,但没动 __hash__,Python 默认使用 id()(即内存地址)生成哈希值。这意味着即使两个对象逻辑上相等(__eq__ 返回 True),它们的哈希值也不同,结果就是:
-
obj1 == obj2是True,但obj1 in {obj2}是False -
{obj1, obj2}包含两个元素,而不是一个 -
{obj1: 'a', obj2: 'b'}是长度为 2 的字典
怎么写安全的 __eq__ 和 __hash__
核心原则:如果两个对象 __eq__ 返回 True,它们的 __hash__ 必须返回相同整数;反之不强制要求(哈希冲突允许)。
立即学习“Python免费学习笔记(深入)”;
实操建议:
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
-
__eq__中优先用is快速判断同一对象,再检查类型,最后逐字段比较(避免==引发递归或意外行为) -
__hash__应只基于__eq__中用到的、不可变字段计算;若对象可变(比如有可修改的属性),应显式设__hash__ = None,禁用哈希(否则会报TypeError) - 推荐用
hash((field1, field2, ...))构造元组再哈希,简洁且符合 Python 惯例
示例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if self is other:
return True
if not isinstance(other, Point):
return False
return self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y))
常见踩坑点:可变对象误加哈希
如果你的类设计上允许修改字段(如 point.x = 10),但又实现了 __hash__,就会出问题:
- 对象加入
set后修改字段 → 哈希值实际变了,但set不知道,导致再也找不到它(既不在原桶也不在新桶) - Python 在这种情况下不会自动更新哈希位置,这是未定义行为
- 正确做法是:只要类有可变状态,就不要实现
__hash__,或明确写__hash__ = None
例如:Point 如果允许修改 x/y,就该加一句:__hash__ = None,这样 hash(p) 会直接抛 TypeError,提前暴露问题。
测试时别漏掉哈希一致性校验
光测 a == b 和 hash(a) == hash(b) 还不够。真正要验证的是:当两个对象相等时,它们在哈希容器中的行为是否一致。
建议补一组小测试:
assert a == bassert hash(a) == hash(b)assert len({a, b}) == 1-
assert {a: 'x', b: 'y'} == {a: 'y'}(注意后者覆盖,因为 key 被视为同一)
这些比单独看方法签名更能暴露逻辑漏洞。尤其要注意,__hash__ 返回值必须是整数,且不能是 float、None 或自定义对象——否则运行时报错,不是语法错误。

















