定义 `__eq__` 的类型是不可哈希的吗?

93 投票
4 回答
18091 浏览
提问于 2025-04-15 15:17

我在把一个功能移植到我程序的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 个回答

8

查看一下Python 3的手册,关于object.__hash__:

如果一个类没有定义__eq__()方法,那么它也不应该定义__hash__()操作;如果它定义了__eq__()但没有定义__hash__(),那么它的实例就不能用作可哈希集合中的项。

这部分是我特别强调的。

如果你想省事,听说可以简单地定义__hash__(self)让它返回id(self):

用户自定义的类默认有__eq__()__hash__()方法;有了这些方法,所有对象之间的比较都是不相等的(除了和它们自己比较),而且x.__hash__()会返回id(x)

33

这段话来自于 http://docs.python.org/3.1/reference/datamodel.html#object.hash

如果一个类重写了 __eq__() 方法,但又想保留父类的 __hash__() 方法的实现,那么需要明确告诉解释器这一点,方法是把 __hash__ 设置为 <ParentClass>.__hash__。否则,__hash__() 的继承就会被阻止,就好像 __hash__ 被明确设置为 None 一样。

104

没错,如果你定义了 __eq__ 这个方法,默认的 __hash__ 方法(也就是根据对象在内存中的地址来生成哈希值)就不再使用了。这一点很重要,因为哈希值需要和相等性保持一致:相等的对象应该有相同的哈希值。

解决这个问题很简单:只需要在定义 __eq__ 的同时,也定义 __hash__

撰写回答