我有一个名为GraphEdge
的类,我希望通过其set
和tail
成员在集合(内置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')])
根据我的定义,该集合无法知道E
和H
是相同的,因为它们具有不同的哈希值(根据我的知识,这是集合类型用于确定唯一性的集合) ,因此它将两者都添加为不同的元素。所以我为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__
(也基于tail
和head
)。
答案 0 :(得分:15)
您有哈希冲突。在哈希冲突时,集合使用==运算符来检查它们是否真正相等。
答案 1 :(得分:7)
了解hash和==如何协同工作非常重要,因为两者都是由集合使用的。对于两个值x和y,重要的规则是:
x == y ==> hash(x) == hash(y)
(x等于y表示x和y的散列相等)。但是,反之亦然:两个不相等的值可以具有相同的散列。
集合(和dicts)将使用散列来获得相等的近似值,但是将使用实数相等操作来确定两个值是否相同。
答案 2 :(得分:6)
如果您至少需要其中一个,则应始终定义__eq__()
和__hash__()
。如果两个对象的哈希值相等,则会进行额外的__eq__()
检查以验证唯一性。