0

Newbie question on super(): In the toy example below, I was surprised that the output is C and not A since J in inherits foo from A. Here is the code:

class A:
    def foo(self):
        return 'A'


class C(A):
    def foo(self):
        return 'C'


class J(A):
    pass


class E(J, C):

    def f(self):
        return super().foo()

    def foo(self):
        return 'E'

print(E().f())

J inherits foo from A; the MRO of E is:

(<class '__main__.E'>, <class '__main__.J'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

so how come 'A' isn't the return value? ie evaluation proceeds to C

Alex
  • 19,533
  • 37
  • 126
  • 195

1 Answers1

3

The class super does not just recover the superclass. It instantiate an object which recovers methods in the context of a given method resolution order. This answer features a reimplementation of super in Python to help you understand how it resolves a method inheritance.

So, as you can see, the method resolution order of your class is (E, J, C, A, object). Since it comes up first, then E inherits foo from C and not A.

Now the question you are probably asking is...

Why does C comes before A?

The C3 linearization algorithm which builds the mro has the property that if some class X inherits from Y, then it will come prior to Y in the mro.

As you can see, this is respected in your example. The class C inherits from A and thus has to come first.

The following example demonstrates that as soon as you were to drop the A inheritance from C, then the result becomes the one you were mistakingly expecting.

class A:
    pass

class C: # Dropped A inheritance
    pass

class J(A):
    pass

class E(J, C):
    pass

E.__mro__ # (E, J, A, C, object)
Olivier Melançon
  • 21,584
  • 4
  • 41
  • 73
  • ok -- i still don't understand why `J` which comes before `A` or `C` and inherits the `foo` from `A` isn't called.. ie the resolution goes to the first class in the MRO which is `J`.. `J` has a `foo` inherited from `A`... why isn't that the method being called? – Alex Apr 16 '18 at 20:15
  • Because super does not look at the class base, it looks at the class mro, let me add on this – Olivier Melançon Apr 16 '18 at 20:18
  • @Alex have a look at the first paragraph. I linked an answer where I developped exactly on the question you are asking. I'm actually realizing this might be a duplicate. – Olivier Melançon Apr 16 '18 at 20:25
  • OK let me take a look at that - appreciate the detailed response – Alex Apr 16 '18 at 20:26
  • I think I may have had a slight misunderstanding of inheritance in Python: when you write `class J(A)` the Python interpreter does not actually create a method in `J.foo` but rather creates a pointer to `A.foo`, thus super simply doesn't follow that pointer.. is that right? – Alex Apr 16 '18 at 20:38
  • It doesn't even do that. It resolves which method to call *when you get the attribute*. You can even [change your base dynamically](https://stackoverflow.com/questions/9539052/how-to-dynamically-change-base-class-of-instances-at-runtime) and it will change the method resolution, proof that no pointer is created. A class is only that: its own methods and its base to resolve inherited methods if a method is not found in the subclass. – Olivier Melançon Apr 16 '18 at 21:55
  • By example, if you access `J.__dict__`, you will not find `foo`. Where to get `foo` from is resolved only when you try to access it, and there the mro comes into play. – Olivier Melançon Apr 16 '18 at 22:13