需同时定义__eq__和__hash__以控制对象相等性与哈希行为:__eq__应显式比较内容并返回true/false,__hash__须配套实现(不可变对象)或设为none(可变对象),否则对象不可哈希;避免用vars()或__dict__深比较,推荐显式字段比对或使用@dataclass自动处理。

用 __eq__ 和 __hash__ 控制对象相等性与哈希行为
Python 默认的 __eq__ 比较的是对象身份(id()),不是内容。想让两个实例在字典键、集合成员或 == 判断中表现一致,必须显式定义 __eq__;一旦定义了它,通常还得补上 __hash__,否则对象会自动变为不可哈希——比如塞不进 set 或当字典键用。
-
__eq__必须返回True或False,别返回None或其他值,否则==会回退到默认行为,结果不可控 - 如果类实例是可变的(比如有随时被改写的属性),别实现
__hash__,或者明确返回NotImplemented,否则哈希值可能随内容变化,破坏字典/集合的内部一致性 - 常见错误:只重写
__eq__,忘了设__hash__ = None或提供对应逻辑,导致TypeError: unhashable type
示例:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
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)) # 元组可哈希,且顺序敏感深比较时慎用 vars() 或 __dict__
用 vars(obj) == vars(other) 看似省事,但容易漏掉父类属性、描述符、@property 计算属性,甚至被 __slots__ 搞得直接报错。
-
__dict__不包含用__slots__声明的属性,也不包含从父类继承来的非覆盖属性 - 如果对象有
@property,比如full_name是基于first和last拼的,那仅比__dict__就会忽略这个逻辑一致性 - 更稳的方式是显式列出需参与一致性校验的字段,比如在
__eq__中逐个比对,或维护一个_fields_for_equality = ('x', 'y', 'label')元组来驱动比较逻辑
用 dataclasses 自动处理一致性(Python 3.7+)
如果你的类本质是数据容器,@dataclass 是最省心的选择:它默认生成 __eq__、__hash__、__repr__,还能控制哪些字段参与比较。
立即学习“Python免费学习笔记(深入)”;
- 加
@dataclass(eq=True, unsafe_hash=False)是默认行为,安全;若要哈希,得显式写unsafe_hash=True(前提是所有字段都可哈希) - 用
field(compare=False)可排除日志、缓存、连接对象等不应影响相等性的字段 - 注意:
default_factory创建的可变对象(如list)会导致多个实例的默认值共享引用,表面一致,实际可能被意外修改——这不是校验逻辑的问题,而是构造阶段的坑
示例:
from dataclasses import dataclass, field <p>@dataclass class Config: host: str port: int timeout: float = 30.0 _cache: dict = field(default_factory=dict, compare=False)
序列化后比对(JSON / pickle)不是真一致性校验
有人图省事把对象 json.dumps(vars(obj)) 再比字符串,这看起来“内容一致”,但掩盖了类型、顺序、空值处理等关键差异。
-
None、float('nan')、Decimal('1.0')在 JSON 里都变成null或1.0,类型信息全丢 - 字典键顺序在 Python 3.7+ 虽保持插入序,但 JSON 规范不保证顺序,不同库 dump 出来可能不一致
-
pickle更危险:不同 Python 版本、不同模块路径、甚至不同导入方式都可能导致序列化结果不同,完全不能用于跨环境一致性判断
真正需要一致性校验的地方,比如测试断言、缓存 key 计算、去重逻辑,必须基于语义定义——而不是序列化后的字节是否相同。
最容易被忽略的一点:一致性 ≠ 相等性。比如两个 datetime 对象毫秒级相同但时区不同,业务上可能算“不一致”;这时候 __eq__ 得按业务规则写,而不是无脑比 ==。










