0

In the below code, i replaced args with numbers to demonstrate what classes are inherited.

class Animal:
  def __init__(self, animalName):
    print(animalName, 'is an animal.');

class Mammal(Animal):
  def __init__(self, mammalName):
    print(mammalName, 'is a mammal.')
    super().__init__(mammalName)

class CannotFly(Mammal):
  def __init__(self, mammalThatCantFly):
    print('2', "cannot fly.")
    super().__init__('2')

class CannotSwim(Mammal):
  def __init__(self, mammalThatCantSwim):
    print('1', "cannot swim.")
    super().__init__('1')

# Cat inherits CannotSwim and CannotFly
class Cat(CannotSwim, CannotFly):
  def __init__(self):
    print('I am a cat.');
    super().__init__('Cat')

cat = Cat()

returns

I am a cat.
1 cannot swim.
2 cannot fly.
2 is a mammal.
2 is an animal.

Why is it not the below?

I am a cat.
1 cannot swim.
1 is a mammal.
1 is an animal.
2 cannot fly.
2 is a mammal.
2 is an animal.

There are effectively two call streams, no?

  • 1
    Look at: `https://en.wikipedia.org/wiki/C3_linearization` – hussic Jan 07 '22 at 12:52
  • 1
    No, there are not "two call streams". You call `Cat`, and each `__init__` makes exactly one call to `super`. Where do you think the extra calls would come from? `super()` just finds the *next* class in the MRO, not all parent classes of the current object. – chepner Jan 07 '22 at 13:00
  • Thanks @hussic and chepner, understood. Out of curiosity, is there a way to find all the parent classes? I mean, why super on CannotSwim(Mammal) does not go to Mammal class. – learning_python_self Jan 07 '22 at 13:03

2 Answers2

1

Take a look at this post What does 'super' do in Python? - difference between super().__init__() and explicit superclass __init__()

Now what it says is that __init__ is called for every class in the instance's mro. you can print that by doing print(Cat.__mro__) this will print out

(<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>)

As you can see there is the order of the calls. Now, why '2' is used and not '1', see the answer in the comments by hussic

Robin Dillen
  • 704
  • 5
  • 11
1

You can see the method resolution order (MRO) for Cat:

>>> Cat.mro()
[<class '__main__.Cat'>, <class '__main__.CannotSwim'>, <class '__main__.CannotFly'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>]

Each class appears once in the MRO, due to the C3 linearization algorithm. Very briefly, this constructs the MRO from the inheritance graph using a few simple rules:

  1. Each class in the graph appears once.
  2. Each class precedes any of its parent classes.
  3. When a class has two parents, the left-to-right order of the parents is preserved.

("Linearization", because it produces a linear ordering of the nodes in the inheritance graph.)

super() is misnamed; a better name would have been something lie nextclass, because it does not use the current class's list of parents, but the MRO of the self argument. When you call Cat, you are seeing the following calls.

  1. Cat.__init__
  2. Cat.__init__ uses super to call CannotSwim.__init__
  3. CannotSwim.__init__ uses super to call CannotFly.__init__
  4. CannotFly.__init__ uses super to call Mammal.__init__
  5. Mammal.__init__ uses super to call Animal.__init__
  6. Animal.__init__ uses super to call object.__init__

object.__init__ does not use super (it "owns" __init__), so the chain ends there.

In particular, note #3: CannotSwim causes its "sibling" in the inheritance graph to be used, not its own parent.

chepner
  • 497,756
  • 71
  • 530
  • 681