定义“__ 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 是否与旧 pickle 相同,从而说明该对象是否在此期间发生了更改。问题是,现在我甚至不能检查给定的对象是否在库中,因为它会引发一个关于该对象不可散列的异常。(因为它有一个 __eq__方法。)我该怎么办?

15646 次浏览

I'm no python expert, but wouldn't it make sense that, when you define a eq-method, you also have to define a hash-method as well (which calculates the hash value for an object) Otherwise, the hashing mechanism wouldn't know if it hit the same object, or a different object with just the same hash-value. Actually, it's the other way around, it'd probably end up computing different hash values for objects considered equal by your __eq__ method.

I have no idea what that hash function is called though, __hash__ perhaps? :)

Yes, if you define __eq__, the default __hash__ (namely, hashing the address of the object in memory) goes away. This is important because hashing needs to be consistent with equality: equal objects need to hash the same.

The solution is simple: just define __hash__ along with defining __eq__.

Check the Python 3 manual on object.__hash__:

If a class does not define an __eq__() method it should not define a __hash__() operation either; if it defines ABC0 but not __hash__(), its instances will not be usable as items in hashable collections.

Emphasis is mine.

If you want to be lazy, it sounds like you can just define __hash__(self) to return id(self):

User-defined classes have __eq__() and __hash__() methods by default; with them, all objects compare unequal (except with themselves) and x.__hash__() returns id(x).

This paragraph from http://docs.python.org/3.1/reference/datamodel.html#object.hash

If a class that overrides __eq__() needs to retain the implementation of __hash__() from a parent class, the interpreter must be told this explicitly by setting __hash__ = <ParentClass>.__hash__. Otherwise the inheritance of __hash__() will be blocked, just as if __hash__ had been explicitly set to None.