Search code examples
pythonpython-3.xhashfrozenset

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


According to https://docs.python.org/2/library/stdtypes.html#frozenset, in 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.

However according to https://docs.python.org/3.4/library/stdtypes.html#frozenset, in Python 3 I can see no information indicating that a frozenset instance (or subclass) should be hashable, just the set/frozenset elements:

Set elements, like dictionary keys, must be hashable.

So, should the following code work for any Python 3 interpreter, or should the last line raise a 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, system 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, using homebrew 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), system 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 - Is this a regression in Python 3, or was it a deliberate design choice?


Solution

  • From the definition of hashable:

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

    You have now implemented a new __eq__ method but not a new __hash__ for NewFrozenSet. Python now can't assume that the above property holds (i.e., that the behaviour of __eq__ and __hash__ matches) so it can't assume the type is hashable. That's why you need to implement both __eq__ and __hash__ to make a type hashable (or don't implement either and use the methods from the parent class).

    For example, if you leave out __eq__ then NewFrozenSet does become hashable:

    class NewFrozenSet(frozenset):
        pass
    

    If this is not correct in your NewFrozenSet then you need to implement both __eq__ and __hash__.