In your example code, Child1
's design is very fragile. If its two base classes share some common base (and call their own parents' __init__
method inside of their own turn), that grandparent class might get initialized more than once, which would be bad if it's computationally costly, or has side effects. This is the "diamond pattern" of inheritance hierarchy that can be troublesome in many languages (like C++) that allow multiple inheritance.
Here's an example of the bad problem that can come up with explicit parent calls, and which super()
is designed to fix:
class Grandparent:
def __init__(self):
print("Grandparent")
class Parent1(Grandparent):
def __init__(self):
print("Parent1")
Grandparent.__init__(self)
class Parent2(Grandparent):
def __init__(self):
print("Parent2")
Grandparent.__init__(self)
class Child(Parent1, Parent2):
def __init__(self):
print("Child")
Parent1.__init__(self)
Parent2.__init__(self)
c = Child() # this causes "Grandparent" to be printed twice!
Python's solution is to use super()
which allows collaborative multiple inheritance. Each class must be set up to use it, including the intermediate base classes! Once you set everything up to use super
, they'll call each other as necessary, and nothing will ever get called more than once.
class Grandparent:
def __init__(self):
print("Grandparent")
class Parent1(Grandparent):
def __init__(self):
print("Parent1")
super().__init__()
class Parent2(Grandparent):
def __init__(self):
print("Parent2")
super().__init__()
class Child(Parent1, Parent2):
def __init__(self):
print("Child")
super().__init__()
c = Child() # no extra "Grandparent" printout this time
In fact, every instance of multiple-inheritance in Python is a diamond shape, with the Grandparent
class of object
(if not something else). The object
class has an __init__
method that takes no arguments (other than self
) and does nothing.
It's important to note that the super().__init__
calls from the ParentX
classes don't always go directly to Grandparent
. When initializing an instance of Parent1
or Parent2
, they will, but when instantiating an instance of Child
, then Parent1.__init__
's call will go to Parent2.__init__
, and only from there will Parent2
's super
call go to Grandparent.__init__
.
Where a super
call resolves to depends on the MRO (Method Resolution Order) of the instance the methods are being called on. A super
call generally means "find this method in the next class in the MRO" relative to where it's called from. You can see the MRO of a class by calling SomeClass.mro()
. For Child
in my examples, it's: [Child, Parent1, Parent2, Grandparent, object]