必须同时重写__eq__和__hash__,因为set去重依赖哈希值而非仅相等判断;若只重写__eq__,对象仍按内存地址哈希,导致逻辑相等的对象无法去重。

为什么重写 __eq__ 后 set 去重还是不生效?
因为 Python 的 set 判断重复不仅看 __eq__,更优先依赖 __hash__。如果只重写了 __eq__ 而没重写 __hash__,对象默认继承自 object 的 __hash__(基于内存地址),导致两个逻辑相等的对象哈希值不同,set 会把它们当成完全不同的元素存下来。
必须同时重写 __eq__ 和 __hash__ 的硬规则
Python 明确要求:若重写了 __eq__,且希望实例可哈希(即能放进 set 或作字典键),就必须显式重写 __hash__;否则实例自动变为不可哈希(TypeError: unhashable type)。
-
__eq__定义“什么算相等”——比如按id和name字段判断 -
__hash__必须保证:相等的对象(a == b为True)必须有相同哈希值(hash(a) == hash(b)) - 推荐用
hash((self.id, self.name))这类元组哈希,简洁且满足一致性 - 一旦定义了
__hash__,就别再修改参与哈希的字段值(否则哈希表索引错乱)
一个安全可用的实现示例
假设有个 User 类,按 user_id 唯一去重:
class User:
def __init__(self, user_id, name):
self.user_id = user_id
self.name = name
<pre class="brush:php;toolbar:false;">def __eq__(self, other):
if not isinstance(other, User):
return False
return self.user_id == other.user_id
def __hash__(self):
return hash(self.user_id)
创建 Flask + Vue.js 全栈项目,采用单端口架构、uv 依赖管理、结构化日志和生产级前端设计。当用户要求以下操作时触发:创建新的 Flask+Vue 项目、搭建 Web 应用、构建全栈 Python+Vue 应用、初始化 Flask Web 项目、设置单端口 Flask 前后端项目。触发词:新建项目、创建项目、搭建项目、Flask项目、Vue项目。
这样就能正确去重:
users = {User(1, "Alice"), User(1, "Bob"), User(2, "Charlie")}
print(len(users)) # 输出 2 —— user_id=1 的两个对象被去重
- 注意
__eq__中的类型检查,避免和非User对象比较时报错或逻辑错误 -
__hash__返回整数,不能是None或浮点数等不可哈希类型 - 如果字段可能为
None,要提前处理,比如hash(self.user_id or 0)
常见翻车现场和应对
实际写的时候最容易忽略的是可变性与哈希稳定性:
- 在对象加入
set后修改了影响__hash__的字段(如self.user_id),会导致该对象再也无法被set.discard()或in查找——因为它现在算在另一个哈希桶里了 - 用可变对象(如
list、dict)参与__hash__计算(例如hash(self.tags)),会直接报TypeError: unhashable type - 误把
__hash__写成return None(这是旧版 Python 的写法,现在会报错) - 多字段组合哈希时顺序写反或漏字段,导致本该相等的对象哈希不一致
真正关键的不是“怎么写”,而是“哪些字段决定了唯一性”——这个业务语义一旦定下,__eq__ 和 __hash__ 就必须严格对齐,且这些字段在对象生命周期内最好不可变。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










