Search code examples
pythonpython-3.xmultiple-inheritance

Understanding Nested Inheritance in Python


Here is a simplified code of my main code illustrating the behaviour I obtain.

Suppose I have a main class (MAIN) and two classes (A,B) inheriting from it. This main class has a method which is overwriten by A but not by B, which means that B inherits the method from main.

Then I have a class D which inherits from A and from B, and has a method which calls the aforementioned method. From what I have understood in the way multiple inheritance work, if I define D as class D(A,B) then if A and B have a shared method, calling D.method() will call A.method, and vice-versa (i.e if class D(B,A) then B.method is called. The following code exemplifies this text.


class MAIN(object):
    def __init__(self):
        pass
    def print(self):
        print('HELLO MAIN')

class A(MAIN):
    def __init__(self):
        pass
    def print(self):
        print('HELLO A')

class B(MAIN):
    def __init__(self):
        pass


class C(A,B):
    def __init__(self):
        pass

    def Cprint(self):
        self.print()

c = C()

c.Cprint()


class C(B,A):
    def __init__(self):
        pass

    def Cprint(self):
        self.print()

c = C()

c.Cprint()

However this code always print 'HELLO A', i.e even in the case class C(B,A) I don't get a HELLO MAIN as I would expect. What is happening here? Thanks so much in advance


Solution

  • The mro is (C, A, B, MAIN) with class C(A, B) and (C, B, A, MAIN) with class C(B, A). In both cases, A is before MAIN. B doesn't define .print, so it doesn't matter.

    The method uplooks works like this: (pseudo code)

    def find_attribute(obj, name):
        if name in obj.__dict__:
            return obj.__dict__[name]
        mro = type(obj).__mro__
        for cls in mro:
            if name in cls.__dict__:
                return cls.__dict__[name] # (Here a bit more magic for descriptors happens)
        raise AttributeError(name)
    

    For the classes this is what their __dict__ look like:

    MAIN.__dict__ = {"print": <method MAIN.print>}
    A.__dict__ = {"print": <method A.print>}
    B.__dict__ = {}
    C.__dict__ = {"Cprint": <method C.Cprint>}
    

    As you can see, B does not have a print defined, so in mro=(C, B, A, MAIN) the first print that does get found is in A.