frozenset 子类的实例是否应该在 Python 3 中可哈希?

Should instances of a frozenset subclass be hashable in Python 3?

根据https://docs.python.org/2/library/stdtypes.html#frozenset,在Python 2:

The frozenset type is immutable and hashable -- its contents cannot be altered after is created; however, it can be used as a dictionary key or as an element of another set.

然而,根据 https://docs.python.org/3.4/library/stdtypes.html#frozenset,在 Python 3 中,我看不到任何信息表明 frozenset 实例(或子类)应该是可哈希的,只有 set/frozenset 元素:

Set elements, like dictionary keys, must be hashable.

那么,下面的代码应该适用于任何 Python 3 解释器,还是最后一行应该引发 TypeError?

# Code under test
class NewFrozenSet(frozenset):
    def __eq__(self, other):
        return True

    # Workaround: Uncomment this override
    # def __hash__(self):
    #     return hash(frozenset(self))

hash(frozenset())
hash(NewFrozenSet())

OSX Yosemite 10.10,系统python2

$ python
Python 2.7.6 (default, Sep  9 2014, 15:04:36)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class NewFrozenSet(frozenset):
...     def __eq__(self, other):
...         return True
...
>>> hash(frozenset())
133156838395276
>>> hash(NewFrozenSet())
133156838395276

OSX Yosemite 10.10,使用自制程序 http://brew.sh/

$ brew install python3
$ python3
Python 3.4.2 (default, Jan  5 2015, 11:57:21)
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.56)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class NewFrozenSet(frozenset):
...     def __eq__(self, other):
...         return True
...
>>> hash(frozenset())
133156838395276
>>> hash(NewFrozenSet())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'NewFrozenSet'
>>>

Ubuntu 14.04.1 LTS (x86_64),系统python3

$ python3
Python 3.4.0 (default, Apr 11 2014, 13:05:11)
[GCC 4.8.2] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> class NewFrozenSet(frozenset):
...     def __eq__(self, other):
...         return True
...
>>> hash(frozenset())
133156838395276
>>> hash(NewFrozenSet())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'NewFrozenSet'
>>>

TL;DR - 这是 Python 3 中的回归,还是故意的设计选择?

根据hashable的定义:

Hashable objects which compare equal must have the same hash value.

您现在已经实现了一个新的 __eq__ 方法,但没有为 NewFrozenSet 实现一个新的 __hash__ 方法。 Python 现在不能假设上面的 属性 成立(即 __eq____hash__ 的行为匹配)所以它不能假设类型是可散列的。这就是为什么您需要同时实现 __eq____hash__ 以使类型可哈希(或者不实现任何一个并使用父 class 中的方法)的原因。

例如,如果您省略 __eq__,那么 NewFrozenSet 会变得可哈希:

class NewFrozenSet(frozenset):
    pass

如果这在您的 NewFrozenSet 中不正确,那么您需要同时实施 __eq____hash__