Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 848
Pankhuri Agarwal Avatar asked Mar 08 '17 07:03

Pankhuri Agarwal


People also ask

What are the issues with diamond inheritance?

The Diamond Problem is an ambiguity that arises in multiple inheritance when two parent classes inherit from the same grandparent class, and both parent classes are inherited by a single child class.

How does Python handle diamond problem?

In Python as all classes inherit from object, potentially multiple copies of object are inherited whenever multiple inheritance is used. That is, the diamond problem occurs even in the simplest of multiple inheritance.

What is Diamond problem in OOP Python?

The "diamond problem" (sometimes referred as the "deadly diamond of death") is the generally used term for an ambiguity that arises when two classes B and C inherit from a superclass A, and another class D inherits from both B and C.

Do we have diamond problem in Python?

The ambiguity of the diamond problem does not exist in python since we have the __mro__ attribute -- Method Resolution Order. Python has the super() function which is used to avoid the use of the base class or the parent class explicitly.


1 Answers

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.

like image 112
Stefan Lin Avatar answered Oct 15 '22 09:10

Stefan Lin