根本原因是仅重载__hash__不够,必须同时重载__eq__且二者逻辑一致:若a == b为True,则hash(a) == hash(b)必须成立;否则哈希相同但不相等的对象仍被视为不同元素。

为什么重载 __hash__ 后 set 还是去不掉重复对象
根本原因是:仅重载 __hash__ 不够,Python 要求「哈希相等的对象必须逻辑相等」。如果两个对象 __hash__ 返回相同值,但 __eq__ 返回 False,它们仍被视为不同元素,set 不会合并。
典型错误写法:
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def __hash__(self):
return hash(self.name) # ❌ 只哈希 name,忽略 age
此时 Person("Alice", 25) 和 Person("Alice", 30) 哈希相同,但默认 __eq__ 比较的是内存地址 → 不相等 → set 里存两个。
- 必须同时重载
__eq__,且逻辑与__hash__保持一致(即:若a == b为True,则hash(a) == hash(b)必须成立) -
__hash__返回值必须是整数,且在整个对象生命周期内不可变(所以不能基于可变属性如list字段计算) - 如果重载了
__eq__但没重载__hash__,Python 会自动将__hash__设为None,导致对象不可哈希、无法放入set或作字典键
怎么写安全的 __hash__ 和 __eq__
核心原则:用**所有参与相等判断的不可变字段**构造哈希,且这些字段在对象创建后不再修改。
正确示例:
class Person:
def __init__(self, name: str, age: int):
self.name = name
self.age = age
<pre class="brush:php;toolbar:false;">def __eq__(self, other):
if not isinstance(other, Person):
return False
return self.name == other.name and self.age == other.age
def __hash__(self):
return hash((self.name, self.age)) # ✅ 元组不可变,且字段与 __eq__ 完全一致
- 使用
hash((a, b))是推荐做法:元组本身可哈希,且能自然组合多个字段 - 避免手动拼接字符串再哈希(如
hash(f"{name}{age}")),易受注入或歧义影响(比如"12"和"1"+"2"结果相同) - 如果类有可选字段(如
id=None),需明确它是否参与相等判断;若不参与,就别放进__hash__
遇到可变属性时怎么办
如果对象天然包含可变字段(如 self.tags: list),又想用它做唯一标识,必须做取舍:
- 方案一(推荐):把关键标识抽成只读属性,例如定义
@property返回冻结后的tuple(self.tags),并在__hash__/__eq__中使用它 - 方案二:放弃用该对象直接进
set,改用frozenset或tuple等可哈希代理值,例如set( (p.name, frozenset(p.tags)) for p in persons ) - 方案三:接受不可哈希,改用列表 + 手动去重(性能差,仅小数据量可用):
[p for i, p in enumerate(persons) if p not in persons[:i]]
切记:不要在 __hash__ 里调用 hash(self.mutable_list) —— 列表不可哈希,会直接报 TypeError: unhashable type: 'list'。
调试哈希冲突和去重失败的常用手段
当 set 表现异常,优先验证三个点:
- 打印
hash(obj)和obj.__eq__(other)的返回值,确认两对象是否真被判定为相等 - 检查对象是否意外被修改过:一旦用于哈希的字段变了,原
set中的哈希槽位就失效,可能导致查不到、甚至破坏内部结构 - 用
sys.getsizeof(your_set)和len(your_set)对比,如果远大于后者,说明存在大量哈希碰撞(虽不影响正确性,但性能堪忧) - 临时加日志到
__hash__和__eq__,观察调用频次和参数,确认逻辑触发路径符合预期
最隐蔽的问题是字段类型隐式变化:比如 age 传入了 numpy.int64,而另一个是 int,== 可能返回 True,但 hash() 结果不同 → 破坏一致性。务必统一基础类型。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











