定义 `__eq__` 的类型是不可哈希的吗?
我在把一个功能移植到我程序的Python 3.1版本时遇到了一个奇怪的bug。我把问题缩小到以下假设:
与Python 2.x不同,在Python 3.x中,如果一个对象有一个__eq__
方法,它就会自动变得不能被哈希。
这是真的吗?
在Python 3.1中发生了什么:
>>> class O(object):
... def __eq__(self, other):
... return 'whatever'
...
>>> o = O()
>>> d = {o: 0}
Traceback (most recent call last):
File "<pyshell#16>", line 1, in <module>
d = {o: 0}
TypeError: unhashable type: 'O'
接下来的问题是,我该如何解决我个人的问题?我有一个叫ChangeTracker
的对象,它存储了一个WeakKeyDictionary
,这个字典指向几个对象,并为每个对象提供它们在过去某个时间点的pickle数据。每当一个已有的对象被检查时,变更跟踪器会告诉我它的新pickle是否与旧的相同,从而判断这个对象在这段时间内是否发生了变化。问题是,现在我甚至无法检查给定的对象是否在库中,因为它会抛出一个关于对象不可哈希的异常。(因为它有一个__eq__
方法。)我该如何解决这个问题呢?
4 个回答
查看一下Python 3的手册,关于object.__hash__
:
如果一个类没有定义
__eq__()
方法,那么它也不应该定义__hash__()
操作;如果它定义了__eq__()
但没有定义__hash__()
,那么它的实例就不能用作可哈希集合中的项。
这部分是我特别强调的。
如果你想省事,听说可以简单地定义__hash__(self)
让它返回id(self)
:
用户自定义的类默认有
__eq__()
和__hash__()
方法;有了这些方法,所有对象之间的比较都是不相等的(除了和它们自己比较),而且x.__hash__()
会返回id(x)
。
这段话来自于 http://docs.python.org/3.1/reference/datamodel.html#object.hash
如果一个类重写了
__eq__()
方法,但又想保留父类的__hash__()
方法的实现,那么需要明确告诉解释器这一点,方法是把__hash__
设置为<ParentClass>.__hash__
。否则,__hash__()
的继承就会被阻止,就好像__hash__
被明确设置为 None 一样。
没错,如果你定义了 __eq__
这个方法,默认的 __hash__
方法(也就是根据对象在内存中的地址来生成哈希值)就不再使用了。这一点很重要,因为哈希值需要和相等性保持一致:相等的对象应该有相同的哈希值。
解决这个问题很简单:只需要在定义 __eq__
的同时,也定义 __hash__
。