Должны ли экземпляры подкласса frozenset быть хэшируемыми в Python 3?

Согласно https://docs.python.org/2/library/stdtypes.html, в Python 2:

Тип frozenset является неизменным и хэшируемым - его содержимое не может быть изменено после создания; однако его можно использовать как ключ словаря или как элемент другого набора.

Однако, согласно https://docs.python.org/3.4/library/stdtypes.html, в Python 3 я не вижу никакой информации, указывающей на то, что экземпляр frozenset (или подкласс) должен быть хешируемым, только элементы set/frozenset:

Элементы набора, например ключи словаря, должны быть хэшируемыми.

Итак, должен ли следующий код работать для любого интерпретатора 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, или это был осознанный выбор дизайна?

1 ответ

Решение

Из определения hashable:

Хэшируемые объекты, которые сравниваются равными, должны иметь одинаковое хеш-значение.

Вы внедрили новый __eq__ метод, но не новый __hash__ за NewFrozenSet, Теперь Python не может предположить, что вышеприведенное свойство выполнено (то есть, что поведение __eq__ а также __hash__ соответствует), поэтому он не может предполагать, что тип является хэшируемым. Вот почему вам нужно реализовать оба __eq__ а также __hash__ сделать тип хэшируемым (или не реализовывать и не использовать методы из родительского класса).

Например, если вы пропустите __eq__ затем NewFrozenSet становится хасируемым:

class NewFrozenSet(frozenset):
    pass

Если это не правильно в вашем NewFrozenSet тогда вам нужно реализовать оба __eq__ а также __hash__,

Другие вопросы по тегам