Search code examples
pythonabc

Solving inheritance contradictions among abc.Sequence, abc.Hashable and list in Python


I'm using version 3.6.3

I'm studying Python collections.abc's inheritance relationships among classes. And I found some contradictory inheritances among list, Sequence and Hashable

As you already know,
1. Sequence inherits Hashable class and
2. list inherits Sequence

from collections import Sequence, Hashable


issubclass(Sequence, Hashable)  # 1.
issubclass(list, Sequence)      # 2.

True
True

From this, as you can possibly think list also inherits Hashable conceptually.

But list is mutable and it doesn't inherit Hashable(means 'you cannot 'hash(some_list)')

issubclass(list, Hashable)


False

I think this inheritance is contradictory. I totally understand list is mutable cause I've used list hundreds of times but inheritance relationship graph doesn't support this concept. What am I wrong or missing?

I wait for your adivce. Thank you.


Solution

  • So it is actually an interesting design feature, but Python subclasses don't actually need to be transitive. Transitive as defined by Google:

    "if a trait is applicable between successive members of a sequence, it must also apply between any two members taken in order. For instance, if A is larger than B, and B is larger than C, then A is larger than C."

    For languages where inheritance is transitive, such as Java, if B inherits A and C inherits B, then C has to inherit A. The set of transitive super classes for C would be A, B, and Object, and the direct super class is B.

    In Python we take a departure from this concept. As you pointed out, Sequence is Hashable and a list is a Sequence, but a list is not Hashable. In fact, list doesn't directly inherit anything (apart from object which every python class inherits).

    # from the PyCharm generated stubs
    class list(object):
        ...
    

    In Python you can use __subclasscheck__ or __subclasshook__ from the metaclass utilities to cause the builtin method issubclass to do interesting things. A meta class is an advanced language feature used to modify basic rules about how a class operates (modifying how issubclass works being a great example). Within the abstract base class metaclass ABCMeta, the __subclasscheck__ method will invoke the __subclasshook__ method on a class if it is defined. You can read a great answer about the uses here.

    Certain ABCMeta classes like Hashable implement __subclasshook__ to not check an inheritance tree, but to check for the presence of a method. This is helpful such that common contracts don't have to be included in every class definition you make.

    For this case, in order to be Hashable you need to define __hash__. However, Python states not to hash on a List because it is mutable and therefore it is specifically omitted from this class.

    class Hashable(metaclass=ABCMeta):
    
        __slots__ = ()
    
        @abstractmethod
        def __hash__(self):
            return 0
    
        @classmethod
        def __subclasshook__(cls, C):
            if cls is Hashable:
                return _check_methods(C, "__hash__")
            return NotImplemented
    
    class list(object):
        ...
        __hash__ = None