为了了解发生了什么,您必须了解
membership test中的操作符对不同类型的操作.
对于列表,这是很简单的,因为什么列表基本上是:有序的数组,不关心重复.在这里进行会员考试的唯一可能方式是迭代列表,并检查每个项目的相等性.这样的事情
# x in lst
for item in lst:
if x == item:
return True
return False
词典有点不同:它们是哈希表,键是唯一的.哈希表要求密钥是可哈希的,这基本上意味着需要一个明确的函数将该对象转换成一个整数.然后使用该哈希值将密钥/值映射放在哈希表中.
由于散列值确定哈希表中放置项目的位置,所以关键是要相同的对象产生相同的散列值.所以下面的含义是必须的:x == y => hash(x)== hash(y).反过来不一定是真的;使不同的对象产生相同的哈希值是非常有效的.
当执行字典的成员资格测试时,字典将首先查找哈希值.如果可以找到它,它将对所发现的所有项目执行相等检查;如果没有找到哈希值,那么它假定它是一个不同的对象:
# x in dct
h = hash(x)
items = getItemsForHash(dct, h)
for item in items:
if x == item:
return True
# items is empty, or no match inside the loop
return False
由于您在使用针对列表的成员资格测试时获得所需的结果,这意味着您的对象正确实现了相等性比较(__eq__).但是,由于您在使用字典时没有得到正确的结果,似乎有一个__hash__实现与等式比较实现不同步:
>>> class SomeType:
def __init__ (self, x):
self.x = x
def __eq__ (self, other):
return self.x == other.x
def __hash__ (self):
# bad hash implementation
return hash(id(self))
>>> l = [SomeType(1)]
>>> d = { SomeType(1): 'x' }
>>> x = SomeType(1)
>>> x in l
True
>>> x in d
False
请注意,对于Python 2(从对象继承的类)的新样式类,此“错误哈希实现”(基于对象标识)是默认值.所以当你不实现自己的__hash__函数时,它仍然使用它.这最终意味着除非__eq__只执行身份检查(默认值),否则散列函数将不同步.
所以解决方案是以与__eq__中使用的规则相一致的方式实现__hash__.例如,如果比较两个成员self.x和self.y,则应该使用这两个成员的复合哈希.最简单的方法是返回这些值的元组的哈希值:
class SomeType (object):
def __init__ (self, x, y):
self.x = x
self.y = y
def __eq__ (self, other):
return self.x == other.x and self.y == other.y
def __hash__ (self):
return hash((self.x, self.y))
请注意,如果它是可变的,则不应使对象发生哈希:
If a class defines mutable objects and implements an __eq__() method, it should not implement __hash__(), since the implementation of hashable collections requires that a key’s hash value is immutable (if the object’s hash value changes, it will be in the wrong hash bucket).