Search code examples
pythonmethod-resolution-order

Diamond inheritance and the MRO


I'm a newbie for MRO and having problem figuring out the logic for these outputs.

Case 1:

class A(object):
  def save(self):
      print "A"

class B(A):
    def save(self):
        print "B"
        super(B, self).save()

class C(A):
  def save(self):
      print "C"
      super(C, self).save()


class D(C, B):
    def save(self):
        print "D"
        super(D,self).save()

D().save()

Output:

D
C
B
A

My question here is how super(C) is calling B.save().

As per MRO: super(C, self) is not about the "base class of C", but about the next class in the MRO list of C. But there is no B in MRO list of C.

Case 2:

class A(object):
  def save(self):
      print "A"

class B(A):
    def save(self):
        print "B"
        super(B, self).save()

class C(A):
  def save(self):
      print "C"
      # removed super call here

class D(C, B):
    def save(self):
        print "D"
        super(D,self).save()

D().save()

Output:

D
C

Case 3:

class A(object):
  def save(self):
      print "A"

class B(object):
    #inherits object now instead of A
    def save(self):
        print "B"
        super(B, self).save()

class C(A):
  def save(self):
      print "C"
      super(C, self).save()

class D(C, B):
    def save(self):
        print "D"
        super(D,self).save()

D().save()

Output:

D
C
A

Question

How is the MRO affected if B is not inheriting from A, but object directly?

Can someone explain the reason behind this?


Solution

  • In order to tackle these questions, you need to, first, understand how do MRO and super() work.

    MRO Python Document - MRO
    In Python, MRO is based on C3 linearization. (There are many examples in wiki and python documents)

    super() (check out Python Document - super())
    According to the Python Document, it says..

    Return a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class. The search order is same as that used by getattr() except that the type itself is skipped.

    Therefore, super() will search based on MRO (classobject.__mro__) and it will be satisfied by either parent class or sibling class of type.

    Back to your cases,
    Case 1
    D.__mro__ = (D, C, B, A, object)
    Thus, the output will be D C B A

    Case 2
    The MRO of D is as the same as D in case 1. ((D, C, B, A, object))
    However, super() will stop, or be satisfied, at C because save() in class C does not have a super function call implemented.

    Case 3
    The MRO of D is equal to (D, C, A, B, object). Therefore, when super() reached save() function in class A, it would consider satisfied. That's the reason why save function in B had never been called.

    Moreover, if you switch the inheritance sequence of D from C, B to B, C. (class D(C, B) to class D(B, C)). Then, when you call D().save(), the output will be D B because D.__mro__ = (D, B, C, A, object) and super() will be satisfied at class B.

    Furthermore, from my understanding, super() is not a function that forces child class object to call all override functions in parent classes. If you want to force your class to call all override functions in its parent classes. You could try to do something like:

    class Base(object):
        def method(self):
            print('Base-method')
    
    class Parent(object):
        def method(self):
            print('Parent-method')
    
    class Child(Parent):
        def method(self):
            print('Child-method')
            super(Child, self).method()
    
    class GrandChild(Child, Base):
        def method(self):
            print('GrandChild-method')
            for base in GrandChild.__bases__:
                base.method(self)
            #super(GrandChild, self).method()
    

    Then, when you call `GrandChild().method(), the output will be:

    GrandChild-method
    Child-method
    Parent-method
    Base-method
    

    Note:
    GrandChild.__bases__ contains only class Child and class Base

    PS.
    By saying satisfied above, I meant there is no more super() calls.