Using structural pattern matching, how do you write a case that matches instances of hashable types?
I've tried:
for obj in [], (), set(), frozenset(), 10, None, dict():
match obj:
case object(__hash__=_):
print('Hashable type: ', type(obj))
case _:
print('Unhashable type: ', type(obj))
However, this gets the wrong answer because every type defines __hash__
whether it is hashable or not:
Hashable type: <class 'list'>
Hashable type: <class 'tuple'>
Hashable type: <class 'set'>
Hashable type: <class 'frozenset'>
Hashable type: <class 'int'>
Hashable type: <class 'NoneType'>
Hashable type: <class 'dict'>
The Hashable abstract base class in collections.abc can recognize types that implement hashing using tests like isinstance(obj, Hashable)
or issubclass(cls, Hashable)
.
According to PEP 622, for a class pattern, "whether a match succeeds or not is determined by the equivalent of an isinstance call."
So, you can use Hashable directly in a class pattern:
from collections.abc import Hashable
for obj in [], (), set(), frozenset(), 10, None, dict():
match obj:
case Hashable():
print('Hashable type: ', type(obj))
case _:
print('Unhashable type:', type(obj))
This produces the desired answer:
Unhashable type: <class 'list'>
Hashable type: <class 'tuple'>
Unhashable type: <class 'set'>
Hashable type: <class 'frozenset'>
Hashable type: <class 'int'>
Hashable type: <class 'NoneType'>
Unhashable type: <class 'dict'>
Hashable only deals with the type of the outermost object. It reports hashability in the sense of "the object's type implements hashing" which is what we usually mean when we say "tuples are hashable". That is also the same sense that is used by abstract base classes and by static typing.
Though Hashable detects whether a type implements _hash_, it can not know what hashing actually does, whether it will succeed, or whether it will give consistent results.
For example, hashing gives inconsistent results for float('NaN')
. Tuples and frozensets are normally hashable but will fail to hash if their components values are unhashable. A class could define __hash__
to always raise an exception.