6

With the file super5.py:

class A:
    def m(self):
        print("m of A called")

class B(A):
    def m(self):
        print("m of B called")
        super().m()

class C(A):
    def m(self):
        print("m of C called")
        super().m()

class D(B,C):
    def m(self):
        print("m of D called")
        super().m()

we can do the following:

>>> from super5 import D
>>> x = D()
>>> x.m()
m of D called
m of B called
m of C called
m of A called

To me, this doesn't make sense, because when I execute x.m(), I expect the following to happen:

  1. The first line of m of D is executed and thus "m of D called" is output.
  2. The second line, super().m() is executed, which first takes us to m of B.
  3. In m of B, "m of B called" is first output, and then, m of A is executed due to the super.m() call in m of B, and "m of A called" is output.
  4. m of C is executed in a fashion analogous to 3.

As you can see, what I expect to see is:

m of D called
m of B called
m of A called
m of C called
m of A called

Why am I wrong? Is python somehow keeping track of the number of super() calls to a particular superclass and limiting the execution to 1?

Sahand
  • 7,980
  • 23
  • 69
  • 137
  • You're describing the behavior that would result if each class called its direct superclasses' methods directly. `super` was designed specifically not to do that. – user2357112 Sep 03 '17 at 21:03
  • As usual, the definitive article on this is Raymond Hettinger's "[`super()` considered super](https://rhettinger.wordpress.com/2011/05/26/super-considered-super/)". – Daniel Roseman Sep 03 '17 at 21:27

1 Answers1

6

No, Python keep a track of all super classes in a special __mro__ attribute (Method Resolution Order in new-style classes):

print(D.__mro__)

You get:

(<class 'D'>, <class 'B'>, <class 'C'>, <class 'A'>, <class 'object'>)

So, when you call super, it follow this list in order.

See this question: What does mro() do?.

Everything is explained in the official document in the chapter "Multiple Inheritance".

For most purposes, in the simplest cases, you can think of the search for attributes inherited from a parent class as depth-first, left-to-right, not searching twice in the same class where there is an overlap in the hierarchy. Thus, if an attribute is not found in DerivedClassName, it is searched for in Base1, then (recursively) in the base classes of Base1, and if it was not found there, it was searched for in Base2, and so on.

In fact, it is slightly more complex than that; the method resolution order changes dynamically to support cooperative calls to super(). This approach is known in some other multiple-inheritance languages as call-next-method and is more powerful than the super call found in single-inheritance languages.

Dynamic ordering is necessary because all cases of multiple inheritance exhibit one or more diamond relationships (where at least one of the parent classes can be accessed through multiple paths from the bottommost class). For example, all classes inherit from object, so any case of multiple inheritance provides more than one path to reach object. To keep the base classes from being accessed more than once, the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class, that calls each parent only once, and that is monotonic (meaning that a class can be subclassed without affecting the precedence order of its parents). Taken together, these properties make it possible to design reliable and extensible classes with multiple inheritance.

Community
  • 1
  • 1
Laurent LAPORTE
  • 21,958
  • 6
  • 58
  • 103
  • Okay, so when we reach `class B` in that list, does it simply ignore the `super()`-call in `m`of `B`? – Sahand Sep 03 '17 at 21:03
  • 1
    @Sandi No, it goes to the next superclass according to the mro. That means if you delete the `super()` call in B, you would not reach C. – syntonym Sep 03 '17 at 21:05
  • 1
    The snippet you posted is missleading, because in this case it is *not* depth first. I think this text is more appropriate: "To keep the base classes from being accessed more than once, the dynamic algorithm linearizes the search order in a way that preserves the left-to-right ordering specified in each class" – syntonym Sep 03 '17 at 21:10
  • To your first comment, syntonym: Okay, so does that mean `D.mro()`is passed along with the `super()` call and each successive `super()` call takes us further along `D.mro()`? – Sahand Sep 03 '17 at 21:12
  • Because if `super()` in `m` of each class leads us along the class's own `mro`, it doesn't make sense that we should see the output we see. – Sahand Sep 03 '17 at 21:18
  • 1
    @Sandi: It goes through `type(self)`'s MRO. – user2357112 Sep 03 '17 at 22:52
  • where self is x (in my example) and type(x) = D, right? – Sahand Sep 03 '17 at 23:23
  • 1
    @Sandi: Correct. – user2357112 Sep 04 '17 at 01:56