5

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.

Stonehead
  • 81
  • 5
  • 2
    Have you seen this blog: https://www.naftaliharris.com/blog/python-subclass-intransitivity/? It looks pretty good, here's a quote, "...Iterable is a "subclass" of Hashable--seriously, because Iterable has the `__hash__` method it inherits from object". – SuperShoot May 06 '19 at 02:01
  • `Sequence` has a `__hash__` method, but according to https://docs.python.org/3.6/library/collections.abc.html#collections-abstract-base-classes, `Sequence` does not inherit from `Hashable`. (Not an answer, just an observation.) – Warren Weckesser May 06 '19 at 02:03
  • @SuperShoot Thank you for your reference. I am a big fan of this kind of approach: Getting all classes in Python and testing all triplet dependency checks. I think I should dive into `metaclass` later. :) – Stonehead May 06 '19 at 02:56
  • I've added that one to my reading list too. Thanks for the good Q and all the best with your Python learning. – SuperShoot May 06 '19 at 03:27

1 Answers1

4

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
flakes
  • 21,558
  • 8
  • 41
  • 88
  • 1
    `class list(object):` doesn't appear anywhere in the actual definition of the `list` class. `class list(object)` appears in the `help(list)` output, and it might appear in PyCharm fake source code or something like that, but the [actual class definition](https://github.com/python/cpython/blob/v3.7.3/Objects/listobject.c) is in C. – user2357112 May 06 '19 at 02:22
  • @user2357112 ah thanks, good point, I'll add that in. It is still true that list only inherits object though; by virtue of being a python object. – flakes May 06 '19 at 02:24
  • Thank you my friend. It's very interesting Python classes doesn't simply comply with conceptual inheritance tree. But can you give me the meaning of `transitive`? I'm not an english speaker so it's kind of confusing to me. – Stonehead May 06 '19 at 02:40
  • @flakes Thank you, your edited answer is more clearer. But it got more issues for me too. Why does your code implement `subclasshook`, not `subclasscheck` and what is `_check_methods`? I got a point and appreciate it. – Stonehead May 06 '19 at 03:02
  • @flakes `subclasshook` and `subclasscheck` are very confusing and difficult to me, but I'll look for information. Thank you anyway :P – Stonehead May 06 '19 at 03:08
  • @Stonehead tried to explain it a bit better. The metaclass documentation is also a great resource https://realpython.com/python-metaclasses/ – flakes May 06 '19 at 03:22
  • 1
    @flakes Please know that I really appreciate your effort to make a non faulty and clearer answer. My Python has improved. Thank you very much :) – Stonehead May 06 '19 at 03:22
  • @flakes Last question: what is the `checkmark`? Do you mean `flag?` – Stonehead May 06 '19 at 03:41