-2
class Mammal(object):
  def __init__(self, name):
    print(name, "Is a mammal")

class canFly(Mammal):
  def __init__(self, canFly_name):
    print(canFly_name, "cannot fly")
    super().__init__(canFly_name)

class canSwim(Mammal):
  def __init__(self, canSwim_name):
    print(canSwim_name, "cannot swim")
    super().__init__(canSwim_name)

class canWagTail(Mammal):
  def __init__(self, canWag_name):
    print(canWag_name, "can wag tail")
    super().__init__(canWag_name)

class Animal(canFly, canSwim, canWagTail):
  def __init__(self, name):
    super().__init__(name)

Carol = Animal("Dog")

I thought this multiple multilevel inheritance code would return the following (the first found constructor of the parent class, just like for any other method):

Dog cannot fly
Dog is a mammal

But the result is as follows:

Dog cannot fly
Dog cannot swim
Dog can wag tail
Dog Is a mammal 

Can someone please explain how constructor calling works using super()? Does it automatically calls ALL the parent constructors? And even if that were the case the output should be:

Dog cannot fly
Dog Is a mammal 
Dog cannot swim
Dog Is a mammal 
Dog can wag tail
Dog Is a mammal 

But this is clearly not the case. Can someone please explain?

rudolfovic
  • 3,163
  • 2
  • 14
  • 38
xyz
  • 1
  • Please, edit your question to fix the indentation of your code snippet – buran Mar 10 '23 at 12:15
  • The key phrase you want to search is "method resolution order". The topic is not focused enough for an SO question. I suggest reading about it in Python docs. – zvone Mar 10 '23 at 12:35

1 Answers1

1

The MRO (Method Resolution Order) for the Animal class is calculated using the C3 linearization algorithm, which takes into account the order of inheritance and resolves any conflicts that arise, even if you call one of the constructors explicitly:

class Animal(canFly, canSwim, canWagTail):
  def __init__(self, name):
    canFly.__init__(self, name)

In this case, the Animal class inherits from the canFly, canSwim, and canWagTail classes, which in turn inherit from the Mammal class. The C3 linearization algorithm determines the MRO for the Animal class as follows:

  1. Animal
  2. canFly
  3. canSwim
  4. canWagTail
  5. Mammal
  6. object

This means that when the Animal class is instantiated with the argument "Dog", the __init__ methods of its superclasses are called in the following order:

  1. __init__ method of canFly: prints "Dog cannot fly" and calls the __init__ method of its next class in the MRO, which is canSwim.

  2. __init__ method of canSwim: prints "Dog cannot swim" and calls the __init__ method of its next class in the MRO, which is canWagTail.

  3. __init__ method of canWagTail: prints "Dog can wag tail" and calls the __init__ method of its next class in the MRO, which is Mammal.

  4. __init__ method of Mammal: prints "Dog Is a mammal".

Therefore, even though only the __init__ method of the canFly class is called explicitly, the MRO ensures that the __init__ methods of all the superclasses are called in the order determined by the C3 linearization algorithm.

I'm not sure it makes a lot of sense to inherit without actually using the constructor but you could modify the __init__ method of the canSwim and canWagTail classes to accept a boolean flag indicating whether to call the superclass constructor or not. If the flag is set to False, the constructor will skip calling the superclass constructor.

Here's an example of how you can modify the canSwim and canWagTail classes:

class canSwim(Mammal):
  def __init__(self, canSwim_name, call_super=True):
    if call_super:
      print(canSwim_name, "cannot swim")
      super().__init__(canSwim_name)

class canWagTail(Mammal):
  def __init__(self, canWag_name, call_super=True):
    if call_super:
      print(canWag_name, "can wag tail")
      super().__init__(canWag_name)

Then, in the __init__ method of the Animal class, you can call the canFly constructor and pass False for the call_super parameter for the canSwim and canWagTail constructors, like this:

class Animal(canFly, canSwim, canWagTail):
  def __init__(self, name):
    canFly.__init__(self, name)
    canSwim.__init__(self, name, False)
    canWagTail.__init__(self, name, False)

Then you should get your desired output:

Dog cannot fly
Dog Is a mammal

Hope this helps :)

rudolfovic
  • 3,163
  • 2
  • 14
  • 38
  • Thank you. I understand that and agree that explicit calling is always a possibility. But I am specifically trying to understand how constructor calling using super works in this specific scenario. I am missing something or its unclear. – xyz Mar 10 '23 at 12:17
  • the above one is a diamond diagram. And the doc says: " (because the order of calls is determined at runtime, because that order adapts to changes in the class hierarchy, and because that order can include sibling classes that are unknown prior to runtime)" But this is generic. The only thing I understand is super() will call its parent and so on to find the FIRST method according to an mro [in a normal case]. And from what you shared I assume it calls ALL constructors in this special case. Is that correct? Can you please if possible walk me through the above example. I am new to python. – xyz Mar 10 '23 at 12:34
  • thank a lot again. much aprecciated. – xyz Mar 10 '23 at 13:33