I am new to Python, and I am really confused about the super function and multiple inheritance.
Here is the toy Code:
class A():
def __init__(self):
print('Call class A')
print('Leave class A')
class C(A):
def __init__(self):
print('Call class C')
A.__init__(self)
print('Leave class C')
class D(A):
def __init__(self):
print('Call class D')
#A.__init__(self)
super(D,self).__init__()
print('Leave class D')
class B(A):
def __init__(self):
print('Call class B')
super(B,self).__init__()
print('Leave class B')
class E(C,B,D):
def __init__(self):
print('Call class E')
B.__init__(self)
#C.__init__(self)
#D.__init__(self)
print('Leave class E')
Then the output is:
Call class E
Call class B
Call class D
Call class A
Leave class A
Leave class D
Leave class B
Leave class E
(<class '__main__.E'>, <class '__main__.C'>, <class '__main__.B'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
where __init__
of Class D is called. However, if I change class E to:
class E(B,C,D):
def __init__(self):
print('Call class E')
B.__init__(self)
#C.__init__(self)
#D.__init__(self)
print('Leave class E')
where the order of B,C,D is changed, then the output is:
Call class E
Call class B
Call class C
Call class A
Leave class A
Leave class C
Leave class B
Leave class E
(<class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class '__main__.A'>, <class 'object'>)
then the __init__
of class D will not be called, but class C's, although class does not use super
function.
Does someone know:
1. In the first code, why __init__
of class D is also called?
2. In the second code, why __init__
of class C is called and __init__
of class D is not?
Failing to use super
consistently means you aren't guaranteeing that the MRO will be walked properly.
In your first example, the explicit call to B.__init__
in E.__init__
ensures that C.__init__
never gets called, because B
comes after C
in the MRO.
In the second example, B
just happens to be the next class after E
, so nothing broke (yet). But then in C.__init__
, you again use A.__init__(self)
instead of super(C, self).__init__()
, which means you jumped straight from C
to A
, bypassing D
which should have come next.
When designing a class hierarchy that uses super
(not just a single class), you have to ensure that every class uses super
to ensure that the MRO selected by the runtime type of self
is followed, rather than hard-coding calls to parent class methods at compile-time.
The correct definition would be
class A:
def __init__(self):
print('Call class A')
super().__init__()
print('Leave class A')
class C(A):
def __init__(self):
print('Call class C')
super().__init__()
print('Leave class C')
class D(A):
def __init__(self):
print('Call class D')
super().__init__()
print('Leave class D')
class B(A):
def __init__(self):
print('Call class B')
super().__init__()
print('Leave class B')
class E(C,B,D):
def __init__(self):
print('Call class E')
super().__init__(self)
print('Leave class E')
In Python 3, some compiler magic provides the "default" arguments when super
is called without any. For example, in E.__init__
, super().__init___()
is equivalent to super(C, self).__init__()
.
Because the value of self
(and thus its type) is constant throughout the chain of calls, each use of super()
refers to the next class in the MRO for type(self)
. In the above example, that would thus produce the output
Call class E
Call class C
Call class B
Call class D
Call class A
Leave class A
Leave class D
Leave class B
Leave class C
Leave class E
If you change the order of base classes in the definition of E
, the MRO (and thus the output) will update automatically without having to change any further code.