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'>
Solution
The Hashable abstract base class in collections.abc can recognize types that implement hashing using tests like
isinstance(obj, Hashable)orissubclass(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:
This produces the desired answer:
Calls to hash() may still fail or be useless
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.