必须同时重写__eq__和__hash__当需按字段值判断相等且支持放入集合或作字典键;__eq__需类型检查与对称性,__hash__须基于不可变字段且与__eq__逻辑一致。

什么时候必须重写 __eq__ 和 __hash__
默认情况下,Python 中自定义类的实例用 == 比较的是身份(即 id() 是否相同),不是内容;放进 set 或作为 dict 键时会报 TypeError: unhashable type。只有当你需要「按字段值判断相等」且「支持放入集合或作字典键」时,才必须同时重写这两个方法。
__eq__ 的实现要点和常见错误
重写 __eq__ 不能只写逻辑,还要注意类型安全和对称性:
- 务必先检查
other类型,避免和不相关类型(如str、int)比较时抛异常或返回错误结果,推荐用isinstance(other, self.__class__) - 不要在
__eq__里调用super().__eq__(other)—— 父类默认实现仍是基于身份的,会导致逻辑混乱 - 如果类有可变字段(如列表、字典),用它们参与比较要格外小心:后续修改会影响已存入
set的对象行为 - 示例:
def __eq__(self, other): if not isinstance(other, Person): return False return self.name == other.name and self.age == other.age为什么
__hash__必须和__eq__保持一致Python 要求:如果
a == b为True,那么hash(a) == hash(b)也必须为True。否则,对象放进set后可能无法被查到,或dict键查找失效。- 一旦重写了
__eq__,Python 会自动将__hash__设为None(使实例不可哈希),所以必须显式定义__hash__ -
__hash__应该只基于__eq__中用到的、不可变的字段计算,比如用hash((self.name, self.age)) - 切忌在
__hash__中使用可变对象(如list字段),否则运行时可能抛TypeError: unhashable type - 如果类设计上就是可变的(比如后期会改
name),那就别实现__hash__,主动放弃哈希能力更安全
实际使用中容易忽略的兼容性细节
重写后仍可能出问题,尤其在继承和第三方库交互时:
- 子类若没重写
__eq__,但父类写了,那子类实例和父类实例比较可能返回False(即使字段相同),因为isinstance(other, Parent)为True,但isinstance(other, Child)在父类__eq__里是False - 使用
dataclasses时,@dataclass(eq=True, unsafe_hash=True)可自动生成两者,但unsafe_hash=True会忽略字段是否可变,风险需自行承担 - 某些 ORM 或序列化库(如
pydantic)有自己的相等逻辑,重写__eq__可能干扰其内部行为,建议先确认文档是否允许
真正难的不是写对这两个方法,而是想清楚:这个对象在业务语义里「什么才算相等」,以及它是否真的应该被哈希 —— 很多时候,不实现
__hash__反而是更干净的选择。 - 一旦重写了
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











