I looked at answers https://stackoverflow.com/a/33469090/11638153 and https://stackoverflow.com/a/27134600 but did not understand what is "indirection." To be specific, if I have classes like below, are there any drawbacks or advantages in hardcoding the parent class __init__()
, as done in class Child1
(why do we need super()
when one can explicitly write __init__()
methods to invoke)?
class Base1:
def __init__(self):
print("Class Base1 init()")
class Base2:
def __init__(self):
print("Class Base2 init()")
class Child1(Base1, Base2):
def __init__(self):
print("Class Child1 init()")
Base1.__init__(self)
Base2.__init__(self)
class Child2(Base1, Base2):
def __init__(self):
print("Class Child2 init()")
super().__init__()
if __name__ == "__main__":
obj1 = Child1()
print("---")
obj2 = Child2()
CodePudding user response:
Roughly, the difference is that ParentClass.__init__(self)
always calls the specifically named class while super().__init__()
makes a runtime computation to determine which class to call.
In many situations, the two ways give the same results. However, super()
is much more powerful. It a multiple inheritance scenario, it can potentially call some class other than the parent of the current class. This article covers the latter scenario is detail.
For single inheritance, the reason to prefer super()
is that that it read nicely and that it makes maintenance easier by computing the parent class.
For multiple inheritance, super()
is the only straightforward way to access parent classes in the order of the MRO for the class of the calling instance. If those words don't mean anything to you, please look at the referenced article.
CodePudding user response:
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]