奇怪的Python集和哈希行为 - 这是如何工作的?

时间:2010-01-29 01:02:44

标签: python hash set

我有一个名为GraphEdge的类,我希望通过其settail成员在集合(内置head类型)中对其进行唯一定义,通过__init__设置。

如果我没有定义__hash__,我会看到以下行为:

>>> E = GraphEdge('A', 'B')
>>> H = GraphEdge('A', 'B')
>>> hash(E)
139731804758160
>>> hash(H)
139731804760784
>>> S = set()
>>> S.add(E)
>>> S.add(H)
>>> S
set([('A', 'B'), ('A', 'B')])

根据我的定义,该集合无法知道EH是相同的,因为它们具有不同的哈希值(根据我的知识,这是集合类型用于确定唯一性的集合) ,因此它将两者都添加为不同的元素。所以我为GraphEdge定义了一个相当天真的哈希函数,如下所示:

def __hash__( self ):
    return hash( self.tail ) ^ hash( self.head )

现在上面的工作正如预期的那样:

>>> E = GraphEdge('A', 'B')
>>> H = GraphEdge('A', 'B')
>>> hash(E)
409150083
>>> hash(H)
409150083
>>> S = set()
>>> S.add(E)
>>> S.add(H)
>>> S
set([('A', 'B')])

但很明显,('A', 'B')('B', 'A')在这种情况下将返回相同的哈希值,因此我希望我无法将('B', 'A')添加到已包含('A', 'B')的集合中。但事实并非如此:

>>> E = GraphEdge('A', 'B')
>>> H = GraphEdge('B', 'A')
>>> hash(E)
409150083
>>> hash(H)
409150083
>>> S = set()
>>> S.add(E)
>>> S.add(H)
>>> S
set([('A', 'B'), ('B', 'A')])

使用哈希的集合类型是否也是如此?如果是这样,最后一个场景怎么可能?如果没有,为什么第一个场景(没有__hash__定义)不起作用?我错过了什么吗?

修改:供将来读者参考,我已定义__eq__(也基于tailhead)。

3 个答案:

答案 0 :(得分:15)

您有哈希冲突。在哈希冲突时,集合使用==运算符来检查它们是否真正相等。

答案 1 :(得分:7)

了解hash和==如何协同工作非常重要,因为两者都是由集合使用的。对于两个值x和y,重要的规则是:

x == y ==> hash(x) == hash(y)

(x等于y表示x和y的散列相等)。但是,反之亦然:两个不相等的值可以具有相同的散列。

集合(和dicts)将使用散列来获得相等的近似值,但是将使用实数相等操作来确定两个值是否相同。

答案 2 :(得分:6)

如果您至少需要其中一个,则应始终定义__eq__()__hash__()。如果两个对象的哈希值相等,则会进行额外的__eq__()检查以验证唯一性。