I'm trying to understand how multiple inheritance (i.e., the C3 algorithm for method resolution order) works in Python. The toy example with the classical diamond dependency below gives me results that are against my intuition.
In particular, I noticed the following:
A
and AA
have super
calls to Base
, then the output is: <B> <A> <AA> <Base> </Base> </AA> </A> </B>
.(1)
is commented out (i.e., no call to Base
constructor in A
), then the output is <B> <A> </A> </B>
.(2)
is commented out (i.e., no call to Base
constructor in AA
), then the output is <B> <A> <AA> </AA> </A> </B>
.(1)
and (2)
are commented out, then the output is <B> <A> </A> </B>
.My questions are:
A
before the explicit call to the Base
constructor, jumps to ("recurses into") the AA
constructor (as if AA
would derive from A
), then descends into Base
, and then back out. Is this what happens? (I understand the MRO B->A->AA->Base
comes from the C3 requirement that child classes be called before parent classes.)AA
never called, although in case 3 it is called?Base
constructor not called, despite an explicit call in the constructor of AA
(case 2) / A
(case 3)? (In example 1 it is called as I would expect.)Here are the MROs of the classes:
B
: (<class '__main__.B'>, <class '__main__.A'>, <class '__main__.AA'>, <class '__main__.Base'>, <type 'object'>)
A
: (<class '__main__.A'>, <class '__main__.Base'>, <type 'object'>)
AA
: (<class '__main__.AA'>, <class '__main__.Base'>, <type 'object'>)
Base
: (<class '__main__.Base'>, <type 'object'>)
Code:
#!/usr/bin/env python
# Using Python 2.7
class Base(object):
def __init__(self):
print '<Base>',
super(Base, self).__init__()
print '</Base>',
class A(Base):
def __init__(self):
print '<A>',
super(A, self).__init__() # (1)
print '</A>',
class AA(Base):
def __init__(self):
print '<AA>',
super(AA, self).__init__() # (2)
print '</AA>',
class B(A, AA):
def __init__(self):
print '<B>',
super(B, self).__init__()
print '</B>',
if __name__ == '__main__':
obj = B()
You shouldn’t see super
as a function call to the next “up” in the inheritance chain. Instead, when properly used, super
will ensure that all functions in the MRO are called in that order. But in order for that top happen, a super call needs to be in every segment of that chain.
So if you remove the super call in either A
or AA
then chain is interrupted. Depending on which you remove, the chain is interrupted either at A
or AA
:
B
, A
, AA
, Base
A
): B
, A
AA
); B
, A
, AA
So you should keep in mind to always consistently use super in all involved types for it to function properly.
If you want to learn more about super
, you should check out Raymond Hettinger’s talk “Super considered super!” at this year’s PyCon. It is very well explained and also has some easy to understand examples (involving real people!).
To quote him from that talk (transcription and emphasis mine):
What’s our biggest problem with
super
in Python? It’s not its design. Its design I think is flawless, it’s beautiful, Guido did an extraordinary job with it.The problem is the name, it shouldn’t have been called “super”. Why not? The answer is, if if you learn super in any other language, it doesn’t do the same as Python.
[…] What does it do in other languages? In other languages, it calls your parents. […] Inheritance, whenever you call
super
is about calling your parents. But Python’s does something different. It does call parents, but not your parents. When you callsuper
, whose parents get called? It is not your ancestors, it’s your children’s ancestors.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With