Search code examples
pythonprotocolshashablehasattrstructural-pattern-matching

How detect hashable types with structural pattern matching?


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

  • Solution

    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'>
    

    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.