Ex42: Super headaches

I’m in the beginning of Ex 42 but I’ve been sidetracked by something in it. I was punching in the code for Ex 42 when I got to the line that had super() in it. I was already somewhat aware of super() having encountered it doing online research on classes but I decided to pause working on 42 to better understand super(). I was reading up on it and found some code as an example. I started playing around with the code to see how I could break it and I found something I cannot explain/understand why it’s happening.

This is the original code:

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

class Mammal(Animal):
  def __init__(self, mammalName):
    print(mammalName, 'is a warm-blooded animal.')
    super().__init__(mammalName)

class NonWingedMammal(Mammal):
  def __init__(self, NonWingedMammal):
    print(NonWingedMammal, "can't fly.")
    super().__init__(NonWingedMammal)

class NonMarineMammal(Mammal):
  def __init__(self, NonMarineMammal):
    print(NonMarineMammal, "can't swim.")
    super().__init__(NonMarineMammal)


class Dog(NonMarineMammal, NonWingedMammal):
  def __init__(self):
    print('Dog has 4 legs.')
    super().__init__('Dog')

d = Dog()
print('')
print(Dog.__mro__)
print('')

I added the lines about printing the MRO in for reasons I’ll get to in a minute.

This code when run produces the following in PowerShell:

PS C:\Users\dswal\AppData\Local\Programs\Python\Python36\Doug> python testout.py
Dog has 4 legs.
Dog can't swim.
Dog can't fly.
Dog is a warm-blooded animal.
Dog is an animal.

(<class '__main__.Dog'>, <class '__main__.NonMarineMammal'>, <class '__main__.NonWingedMammal'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>)

While I was messing with the code I moved all the super() lines above the print() lines in each method that had super() in it. So it looked like this…

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

class Mammal(Animal):
  def __init__(self, mammalName):
    super().__init__(mammalName)
    print(mammalName, 'is a warm-blooded animal.')

class NonWingedMammal(Mammal):
  def __init__(self, NonWingedMammal):
    super().__init__(NonWingedMammal)
    print(NonWingedMammal, "can't fly.")

class NonMarineMammal(Mammal):
  def __init__(self, NonMarineMammal):
    super().__init__(NonMarineMammal)
    print(NonMarineMammal, "can't swim.")


class Dog(NonMarineMammal, NonWingedMammal):
  def __init__(self):
    super().__init__('Dog')
    print('Dog has 4 legs.')

d = Dog()
print('')
print(Dog.__mro__)
print('')

This modification, when run in PowerShell, produces the following:

PS C:\Users\dswal\AppData\Local\Programs\Python\Python36\Doug> python testout.py
Dog is an animal.
Dog is a warm-blooded animal.
Dog can't fly.
Dog can't swim.
Dog has 4 legs.

(<class '__main__.Dog'>, <class '__main__.NonMarineMammal'>, <class '__main__.NonWingedMammal'>, <class '__main__.Mammal'>, <class '__main__.Animal'>, <class 'object'>)

Moving the super() above the print() statement in each method reverses the order of the prints in PowerShell. I’m sure there’s a logical reason for this but what blows my mind is that the print order reverses despite the MRO not changing.

Can someone explain to me why this is happening? I’m not getting it.

Actually I may have just figured out why in my head but I want to hear the official reason first to see if my deduction was correct…

super is calling itself on the bottom code, so it does not print until it hits the last class where it triggers a loop by calling super().__init__('Dog'). Just a guess, but I wanted to know the answer too. lol

I think you’ve got it already. By moving the super call to the top of the function you’re basically saying: Tell me about the parent class first, then say what this particular class is about. This continues “recursively” (not really, but in a way) up to the top, so you end up getting the most general info first.

1 Like

That’s what I figured it was doing. Which I guess means I could use super to force the MRO to do things differently from the way the MRO would normally play out if super was at the end of the functions or not present at all.

I think I can see why super() and multi-inheritance could be such a nightmare. Without proper documentation/commenting in the code it could be deucedly difficult to figure out what the execution order is going to be if you have to trace your way back through the classes manually.

1 Like

Don’t go there… it’ll end in tears.

Yes, I agree with @florian here. I have been coding for 34 or 27 years depending on how you look at it and in all that time I’ve never needed multiple inheritance. It’s one of those theoretical things you can do, but that nobody actually needs to do.

The reason to use super is so that child classes that may be using cooperative multiple inheritance will call the correct next parent class function in the Method Resolution Order (MRO).

In Python 3, we can call it like this:

class ChildB(Base):
    def __init__(self):
        super().__init__()

In Python 2, you were required to call super like this with the defining class’s name and self, but you’ll avoid this from now on because it’s redundant, slower (due to the name lookups), and more verbose (so update your Python if you haven’t already!):

super(ChildB, self).__init__()

Without super, you are limited in your ability to use multiple inheritance because you hard-wire the next parent’s call:

Base.__init__(self) # Avoid this.