唯一必需的属性是相等的对象具有相同的哈希值。
“规范文本中的‘比较相等’是指它们的__eq__方法的结果——没有要求它们是同一个对象。”
“__hash__”必须基于在“__eq__”中使用的值,而不是对象的“id”——你代码中这部分是错误的。为了让它工作,它必须是这样的:
只需要执行:
...
def __eq__( self, b ):
return self.v[0] == b.v[0] and self.v[1] == b.v[1]
def __hash__( self ):
return hash((self.v[0], self.v[1]))
这是否意味着两个不同但等价的对象不能用作字典中的不同键,或者单独出现在集合中?
是的。这就是规范的含义。
解决方法是保留类的默认__eq__
实现以符合规则,并实现一种替代比较形式,在代码中使用它。
最简单的方法是保留__eq__
的默认实现,即按标识比较,并使用任意方法进行比较(在不支持运算符重载的语言中,代码必须使用的习惯用法):
class A( object ):
...
def equals( self, b ):
return self.v[0] == b.v[0] and self.v[1] == b.v[1]
p = A( 1, 0 )
q = A( 1, 0 )
print( str( p ), str( q ) )
print( "identical?", p is q )
print( "equivalent?", p.equals(q) )
有一些方法可以稍微改善这个问题 - 但基本线是:__eq__
必须符合规则,并进行身份比较。
一种方法是拥有一个内部关联对象,它作为一个“命名空间”,您可以用于比较:
class CompareSpace:
def __init__(self, parent):
self.parent = parent
def __eq__( self, other ):
other = other.parent if isinstance(other, type(self)) else other
return self.parent.v[0] == other.v[0] and other.v[1] == b.parent.v[1]
class A:
def __init__( self, v1, v2 ):
self.v = ( v1, v2 )
self.comp = CompareSpace(self)
def __str__( self ):
return str( self.v )
p = A( 1, 0 )
q = A( 1, 0 )
print( str( p ), str( q ) )
print( "identical?", p is q )
print( "equivalent?", p.comp == q )
print( "hashes", hash(p), hash(q) )
破碎演示
现在我将插入一个更加破碎的类的例子,以确保问题在第一次尝试时出现。但是,即使问题每200万次发生一次,你的代码仍然无法用于任何真实的应用,即使是个人代码:你将拥有一个不确定性的字典:
class Broken:
def __init__(self, name):
self.name = name
def __hash__(self):
return id(self) % 256
def __eq__(self, other):
return True
def __repr__(self):
return self.name
In [23]: objs = [Broken(f"{i:02d}") for i in range(64)]
In [24]: print(objs)
[00, 01, 02, 03, 04, 05, 06, 07, 08, 09, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63]
In [25]: test = {}
In [26]: for obj in objs:
...: if obj not in test:
...: test[obj] = 0
...:
In [27]: print(test)
{00: 0, 01: 0, 02: 0, 11: 0}
In [29]: test = {obj: i for i, obj in enumerate(objs)}
In [30]: test
Out[30]: {00: 57, 01: 62, 02: 63, 11: 60}
(我再次强调,虽然您的值本身不会发生冲突,但内部字典代码必须将哈希中的数字减少到其哈希表中的索引 - 不一定是通过模(%) - 否则每个空字典都需要2 ** 64个空条目,仅当所有哈希值仅为64位宽时才需要。)
d[A(1, 0)]
很可能是一个键错误,即使有(两个!)与该值匹配的键。请注意,既没有使用__eq__
也没有使用__hash__
来确定身份,您不能覆盖id
的结果。 - jonrsharpe__eq __()
返回True,则__hash __()
必须为两个对象返回相同的值,否则dict/set永远不会调用这些对象上的__eq __()
。 - jasonharper