Resolving Diamond Inheritance within Python Classes

11,666

Solution 1

so, when you call super from the grandchild, ChildA's __init__ method will be called because super follows the __mro__ property (parents left to right then grandparents left-to-right, then great grandparents, ...)

Since ChildA's init also calls super, then all the super calls will be chained, calling child b's __init__ and eventually the parent init.

For that to work, your interface generally needs to be consistent. That is positional arguments need to mean the same things, and be in the order.

In situations where that's not the case, keyword arguments may work better.

class Parent:    
    def __init__(self, name, serial, **kwargs):
        self.name = name
        self.serial = serial

class ChildA(Parent):    
    def __init__(self, a_name, a_serial, **kwargs):
        self.a_name = a_name
        self.a_serial = a_serial
        super().__init__(**kwargs)

class ChildB(Parent):    
    def __init__(self, b_name, b_serial, **kwargs):
        self.b_name = b_name
        self.b_serial = b_serial
        super().__init__(**kwargs)


class GrandChild(ChildA, ChildB):
    def __init__(self):
        super().__init__(name = "blah", a_name = "a blah", b_name = "b blah", a_serial = 99, b_serial = 99, serial = 30)

Also note that in your code name and serial are reused as instance properties between all the classes and that's probably not what you want.

Solution 2

In python, you can explicitly call a particular method on (one of) your parent class(es):

ChildA.__init__(self, a_name, a_serial)
ChildB.__init__(self, b_name, b_serial)

Note that you need to put the self in explicitly when calling this way.

You can also – as you did – use the super() way, which will call the "first" parent. The exact order is dynamic, but by default it will do left-to-right, depth-first, pre-order scans of your inheritance hierarchy. Hence, your super() call will only call __init__ on ChildA.

Share:
11,666
Zoey
Author by

Zoey

Updated on June 14, 2022

Comments

  • Zoey
    Zoey almost 2 years

    Consider the following python code:

    class Parent(object):
        def __init__(self, name, serial_number):
            self.name = name
            self.serial_number = serial_number
    
    
    class ChildA(Parent):
        def __init__(self, name, serial_number):
            self.name = name
            self.serial_number = serial_number
            super(ChildA, self).__init__(name = self.name, serial_number = self.serial_number)
    
        def speak(self):
            print("I am from Child A")
    
    
    class ChildB(Parent):
        def __init__(self, name, serial_number):
            self.name = name
            self.serial_number = serial_number
            super(ChildB, self).__init__(name = self.name, serial_number = self.serial_number)
    
        def speak(self):
            print("I am from Child B")
    
    
    class GrandChild(ChildA, ChildB):
        def __init__(self, a_name, b_name, a_serial_number, b_serial_number):
            self.a_name = a_name
            self.b_name = b_name
            self.a_serial_number = a_serial_number
            self.b_serial_number = b_serial_number
            super(GrandChild, self).__init_( something )
    

    When running the super function in GrandChild, what is the proper way to format the __init__ arguments so that ChildA and ChildB both get the correct arguments?

    Also how do you access the two different versions of the speak method (ChildA's version and ChildB's version) from within the GrandChild class?

  • Zoey
    Zoey over 6 years
    When I call super in the GrandChild class it does call both ChildA and ChildB's init function. The way I verified this was by putting print statements in both init funcitons and both were printed out.
  • Sam Hartman
    Sam Hartman over 6 years
    I don't think the order is ltr depth-first, because that would call the grand parent between the first child and the second child and that's not what happens. Each super call will only call one new method, but it chains when each called method also calls super.
  • Niobos
    Niobos over 6 years
    I clarified the scanning order. The order is L->R, depth first (it will try the grandparent before trying the 2nd parent), but it will try the parent before the grantparent (pre-order scanning).