0

I have a program something like this :

class A():
    def go(self):
        print("A")


class B(A):
    def go(self):
        super().go()
        print("B")


class C(A):
    def go(self):
        super().go()
        print("C")


class D(A):
    def go(self):
        super().go()
        print("D")


class E(B, C, D):
    def go(self):
        super().go()
        print("E")


a  = A()
b = B()
c = C()
d = D()
e = E()

print(e.go())

Here is the output to this:

A
D
C
B
E
None

I was curious about the workflow of super(), how does it print D, C before B and why is None printed at last? A detailed explanation would be much appreciated.

Anmol Gulati
  • 83
  • 2
  • 9
  • 4
    Have you read any of the numerous existing resources on this? For example, http://stackoverflow.com/q/3277367/3001761. What specifically do you still not understand? – jonrsharpe Apr 23 '17 at 16:49
  • @cricket_007 because it's next in the MRO after A, that's why you put mix-ins before the actual base class (see e.g. http://stackoverflow.com/q/10018757/3001761). – jonrsharpe Apr 23 '17 at 16:57
  • 1
    See Raymond Hettinger's excellent blog post, Super Considered Super, https://rhettinger.wordpress.com/2011/05/26/super-considered-super/ and the talk from PyCon 2015: https://www.youtube.com/watch?v=EiOglTERPEo. You see the order D, C, B because that is how Python's Method Resolution Order (MRO) works. – PurpleDiane Apr 23 '17 at 16:59
  • @jon I missed that init wasn't defined and only e.go() is ran – OneCricketeer Apr 23 '17 at 17:02
  • I thought I understood how `super` worked and then this question comes and proves me wrong. Time to avoid `super` like the plague _again_. – Aran-Fey Apr 23 '17 at 17:10

1 Answers1

2

If you call help(e) you can see the method resolution order:

>>> e = E()
>>> help(e)
class E(B, C, D)
 |  Method resolution order:
 |      E
 |      B
 |      C
 |      D
 |      A
 |      builtins.object

Since you first call super and then print the output will be exactly the reversed mro:

A -> D -> C -> B -> E

If you had the prints before the super-calls it would follow the MRO:

E -> B -> C -> D -> A

However the E.go doesn't return anything (return None is implicit if there's no other return before) so it will print None after all the methods have been called.

MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • Yes, I understood the MRO part. But shouldn't B be printed after A, as B has completed it's super() call and now it should print its own part? – Anmol Gulati Apr 23 '17 at 17:05
  • @AnmolGulati The MRO is always **linear** even if the inheritance is not linear. So when you use `super()` it's always along the MRO. – MSeifert Apr 23 '17 at 17:11
  • @AnmolGulati The thing is that python magically inserts `C` and `D` _between_ `B` and `A`. It's a terrible design choice IMO. – Aran-Fey Apr 23 '17 at 17:12
  • @Rawing Well, you could think it's a terrible design choice but this way you get "dependency injection" built into the basic inheritance. You don't need to rely on frameworks to handle it like in other languages. But it's sometimes confusing - I always need to check the `help` to figure out if the inheritance is working as intended as well :) If you don't want to be confused: avoid multiple inheritance - single inheritance is easy to understand. – MSeifert Apr 23 '17 at 17:14
  • @MSeifert Can you explain a bit further, please? Thanks. – Anmol Gulati Apr 23 '17 at 17:15
  • @MSeifert --> "The MRO is always linear even if the inheritance is not linear. So when you use super() it's always along the MRO". This part. – Anmol Gulati Apr 23 '17 at 17:23
  • @AnmolGulati As soon as you call the method python looks at the MRO of the class you called. The MRO is then fixed - It won't look at the MRO of the individual classes. It only follows the MRO of the class you called the method on. And for diamond inheritance remember that the MRO is _roughly_: siblings first (`B`, `C` and `D`) - parents last (`A`). – MSeifert Apr 23 '17 at 17:31
  • @MSeifert Thanks :-) – Anmol Gulati Apr 23 '17 at 17:35