Python字典中使用对象作为键

33

我想在Python字典中使用一个对象作为键,但它的行为让我无法完全理解。

首先,我创建了一个以我的对象作为键的字典:

package_disseminators = {
  ContentType("application", "zip", "http://other/property") : "one",
  ContentType("application", "zip") : "two"
}

现在创建另一个对象,它与作为键的对象“相同”。

content_type = ContentType("application", "zip", "http://other/property")

我已经为ContentType对象添加了自定义的 __eq__ 和自定义的 __str__ 方法,使得 __eq__ 方法比较 __str__ 值。

现在来一些交互式Python代码:

>>> for key in package_disseminators:
...     if key == content_type:
...             print "match"
...     else:
...             print "no match"
... 
no match
match

>>> content_type in package_disseminators.keys()
True

好的,看起来我的对象被正确识别为键了,所以:

>>> package_disseminators[content_type]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
KeyError: (& (type="application/zip") (packaging="http://other/property") )

嗯...好的?所以content_type在package_disseminators.keys()列表中,但不是一个键?

>>> package_disseminators.has_key(content_type)
False

显然不是这样。

我猜测Python在确定列表中是否包含某个元素和在字典中查找键的相等比较过程不同,但我不知道具体差别。有什么提示或见解吗?

2个回答

44

根据Python文档:

字典的键几乎可以是任意值。但那些不可哈希化的值,也就是包含列表、字典或其他可变类型(这些类型被按值而非对象标识进行比较)的值,则不能用作键。

可哈希化的定义如下:

一个对象如果在其生命周期内具有不变的哈希值(需要一个__hash__()方法),并且可以与其他对象进行比较(需要一个__eq__()__cmp__()方法),则该对象是可哈希的。哈希相等的可哈希对象必须具有相同的哈希值。

由于这些数据结构在内部使用哈希值,因此可哈希对象可以用作字典键和集合成员。

因此,如果要这样做,您需要覆盖对象上的默认__hash__()方法(有关更多解释,请参见Steven Rumbalski的评论)。


>>> content_type in package_disseminators.keys()
True

我想这可以工作是因为 dict.keys() 返回一个列表,而 __contains__ 可能会检查相等性,但不检查相同的哈希值。


16
进一步澄清:你的对象已经从 object 继承了一个 __hash__ 方法。但是默认实现为每个实例返回一个唯一值,因此除非你提供更好的实现,否则两个相等的实例将具有不同的哈希值。has_key 比较哈希值,而 in 检查相等性,这就是为什么在你的示例中 has_key 失败而 in 成功的原因。 - Steven Rumbalski

36

由于字典在内部实现上是哈希表,因此需要同时定义__eq____hash__才能正常工作。

基本的经验法则是:

  • 对于__eq__比较相等的对象,__hash__必须返回相同的哈希值。

根据您的描述,应该类似于以下内容:

def __hash__(self):
    return hash(str(self))

应该可以正常工作。


5
感谢实现 __hash__!不幸的是,我不能给一个问题分配两个正确答案,虽然你的和其他人的结合正是我所需要的一切。祝好,R. - Richard J

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接