Python集合底层使用动态哈希表,要求元素可哈希且需同时重写__hash__和__eq__;平均时间复杂度O(1),依赖哈希定位与桶内等价判断实现去重与查找。
Python的set底层是动态哈希表(类似字典但只存键),所有操作都依赖哈希值。这意味着:set要求元素必须可哈希(即实现__hash__且__eq__一致),不可变类型如int、str、tuple可以,而list、dict、set本身不行。
插入和查找平均时间复杂度是 O(1),最坏情况(大量哈希冲突)退化为 O(n),但CPython做了开放寻址+探查序列优化,实际极少触发。
set比逐个.add()更高效__eq__逐个比较判等set去重不靠遍历比较,而是靠哈希定位 + 精确判等。当你写s = {1, 1, 2},解释器对每个字面量计算hash(1),发现已存在同哈希桶且1 == 1为真,就跳过插入。
注意:如果两个对象hash(a) == hash(b)但a != b(哈希碰撞),它们仍会被视为不同元素存入集合;只有当hash相等 且 ==为真时才去重。
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
只重写__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行为就会出错:要么无法去重,要么去重过度,甚至运行时报错。