Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python multiple inheritance and super()

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:

  1. When run as-is (i.e., both A and AA have super calls to Base, then the output is: <B> <A> <AA> <Base> </Base> </AA> </A> </B>.
  2. When only the the line marked (1) is commented out (i.e., no call to Base constructor in A), then the output is <B> <A> </A> </B>.
  3. When only the the line marked (2) is commented out (i.e., no call to Base constructor in AA), then the output is <B> <A> <AA> </AA> </A> </B>.
  4. When both lines marked (1) and (2) are commented out, then the output is <B> <A> </A> </B>.

My questions are:

  • In case 1, it seems that execution is interrupted in the constructor of 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.)
  • In case 2, why is the constructor of AA never called, although in case 3 it is called?
  • In both cases 2 and 3, why is the 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:

  • Class B: (<class '__main__.B'>, <class '__main__.A'>, <class '__main__.AA'>, <class '__main__.Base'>, <type 'object'>)
  • Class A: (<class '__main__.A'>, <class '__main__.Base'>, <type 'object'>)
  • Class AA: (<class '__main__.AA'>, <class '__main__.Base'>, <type 'object'>)
  • Class 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()
like image 239
Powerfool Avatar asked Oct 30 '22 15:10

Powerfool


1 Answers

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:

  • Uninterrupted (full MRO): B, A, AA, Base
  • Case 1 (without super call in A): B, A
  • Case 2 (without super call in 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 call super, whose parents get called? It is not your ancestors, it’s your children’s ancestors.

like image 191
poke Avatar answered Nov 15 '22 03:11

poke