Resolving Diamond Inheritance within Python Classes
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
.
Zoey
Updated on June 14, 2022Comments
-
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 over 6 yearsWhen 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 over 6 yearsI 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 over 6 yearsI 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).