Python集合底层使用动态哈希表,要求元素可哈希且需同时重写__hash__和__eq__;平均时间复杂度O(1),依赖哈希定位与桶内等价判断实现去重与查找。

Python集合用的是哈希表,不是树或链表
Python的set底层是动态哈希表(类似字典但只存键),所有操作都依赖哈希值。这意味着:set要求元素必须可哈希(即实现__hash__且__eq__一致),不可变类型如int、str、tuple可以,而list、dict、set本身不行。
插入和查找平均时间复杂度是 O(1),最坏情况(大量哈希冲突)退化为 O(n),但CPython做了开放寻址+探查序列优化,实际极少触发。
- 哈希表初始大小为 8,负载因子超过 2/3 时自动扩容(翻倍)
- 每次扩容需重哈希全部元素,所以批量构建
set比逐个.add()更高效 - 相同哈希值的元素会落在同一“桶”里,再用
__eq__逐个比较判等
去重本质是哈希冲突处理 + 等价判断
set去重不靠遍历比较,而是靠哈希定位 + 精确判等。当你写s = {1, 1, 2},解释器对每个字面量计算hash(1),发现已存在同哈希桶且1 == 1为真,就跳过插入。
注意:如果两个对象hash(a) == hash(b)但a != b(哈希碰撞),它们仍会被视为不同元素存入集合;只有当hash相等 且 ==为真时才去重。
立即学习“Python免费学习笔记(深入)”;
class BadHash:
def __init__(self, x):
self.x = x
def __hash__(self):
return 1 # 故意全撞到同一个桶
def __eq__(self, other):
return self.x == other.x
s = {BadHash(1), BadHash(1), BadHash(2)}
print(len(s)) # 输出 2 —— 去重生效,靠的是 eq,不是 hash 单独决定
查找操作就是一次哈希定位 + 桶内线性比较
执行x in s时,CPython先算hash(x),映射到对应桶索引,然后在该桶中遍历已存元素,对每个元素e检查hash(x) == hash(e)且x == e。只要满足两者,立刻返回True。
- 如果
x不可哈希(比如传入list),直接抛TypeError: unhashable type - 如果桶为空或遍历完无匹配,返回
False - 桶内比较次数取决于哈希分布质量,与集合总大小无关——这是 O(1) 的来源
自定义类进set必须同时重写__hash__和__eq__
只重写__eq__会让实例默认继承object.__hash__(基于id),导致逻辑矛盾:两个__eq__为真的对象可能有不同哈希值,从而被当作不同元素存入set;反之只重写__hash__不重__eq__,则所有同哈希对象都被视为相等,破坏语义。
class Point:
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, other):
return isinstance(other, Point) and self.x == other.x and self.y == other.y
def __hash__(self):
return hash((self.x, self.y)) # 元组可哈希,且能反映等价性
p1 = Point(1, 2)
p2 = Point(1, 2)
s = {p1, p2}
print(len(s)) # 输出 1 —— 正确去重
漏掉任一方法,set行为就会出错:要么无法去重,要么去重过度,甚至运行时报错。










