How Does Python's "Super" Do the Right Thing

How does Python's super do the right thing?

I have provided a bunch of links below, that answer your question in more detail and more precisely than I can ever hope to. I will however give an answer to your question in my own words as well, to save you some time. I'll put it in points -

  1. super is a builtin function, not an attribute.
  2. Every type (class) in Python has an __mro__ attribute, that stores the method resolution order of that particular instance.
  3. Each call to super is of the form super(type[, object-or-type]). Let us assume that the second attribute is an object for the moment.
  4. At the starting point of super calls, the object is of the type of the Derived class (say DC).
  5. super looks for methods that match (in your case __init__) in the classes in the MRO, after the class specified as the first argument (in this case classes after DC).
  6. When the matching method is found (say in class BC1), it is called.

    (This method should use super, so I am assuming it does - See Python's super is nifty but can't be used - link below)
    That method then causes a search in the object's class' MRO for the next method, to the right of BC1.
  7. Rinse wash repeat till all methods are found and called.

Explanation for your example

 MRO: D,B,C,A,object  
  1. super(D, self).__init__() is called. isinstance(self, D) => True
  2. Search for next method in the MRO in classes to the right of D.

    B.__init__ found and called



  1. B.__init__ calls super(B, self).__init__().

    isinstance(self, B) => False

    isinstance(self, D) => True

  2. Thus, the MRO is the same, but the search continues to the right of B i.e. C,A,object are searched one by one. The next __init__ found is called.

  3. And so on and so forth.

An explanation of super
http://www.python.org/download/releases/2.2.3/descrintro/#cooperation

Things to watch for when using super
http://fuhm.net/super-harmful/

Pythons MRO Algorithm:
http://www.python.org/download/releases/2.3/mro/

super's docs:
http://docs.python.org/library/functions.html

The bottom of this page has a nice section on super:
http://docstore.mik.ua/orelly/other/python/0596001886_pythonian-chp-5-sect-2.html

I hope this helps clear it up.

What does 'super' do in Python? - difference between super().__init__() and explicit superclass __init__()

The benefits of super() in single-inheritance are minimal -- mostly, you don't have to hard-code the name of the base class into every method that uses its parent methods.

However, it's almost impossible to use multiple-inheritance without super(). This includes common idioms like mixins, interfaces, abstract classes, etc. This extends to code that later extends yours. If somebody later wanted to write a class that extended Child and a mixin, their code would not work properly.

How does Python's super() work with multiple inheritance?

This is detailed with a reasonable amount of detail by Guido himself in his blog post Method Resolution Order (including two earlier attempts).

In your example, Third() will call First.__init__. Python looks for each attribute in the class's parents as they are listed left to right. In this case, we are looking for __init__. So, if you define

class Third(First, Second):
...

Python will start by looking at First, and, if First doesn't have the attribute, then it will look at Second.

This situation becomes more complex when inheritance starts crossing paths (for example if First inherited from Second). Read the link above for more details, but, in a nutshell, Python will try to maintain the order in which each class appears on the inheritance list, starting with the child class itself.

So, for instance, if you had:

class First(object):
def __init__(self):
print "first"

class Second(First):
def __init__(self):
print "second"

class Third(First):
def __init__(self):
print "third"

class Fourth(Second, Third):
def __init__(self):
super(Fourth, self).__init__()
print "that's it"

the MRO would be [Fourth, Second, Third, First].

By the way: if Python cannot find a coherent method resolution order, it'll raise an exception, instead of falling back to behavior which might surprise the user.

Example of an ambiguous MRO:

class First(object):
def __init__(self):
print "first"

class Second(First):
def __init__(self):
print "second"

class Third(First, Second):
def __init__(self):
print "third"

Should Third's MRO be [First, Second] or [Second, First]? There's no obvious expectation, and Python will raise an error:

TypeError: Error when calling the metaclass bases
Cannot create a consistent method resolution order (MRO) for bases Second, First

Why do the examples above lack super() calls? The point of the examples is to show how the MRO is constructed. They are not intended to print "first\nsecond\third" or whatever. You can – and should, of course, play around with the example, add super() calls, see what happens, and gain a deeper understanding of Python's inheritance model. But my goal here is to keep it simple and show how the MRO is built. And it is built as I explained:

>>> Fourth.__mro__
(<class '__main__.Fourth'>,
<class '__main__.Second'>, <class '__main__.Third'>,
<class '__main__.First'>,
<type 'object'>)

Understanding Python super() with __init__() methods

super() lets you avoid referring to the base class explicitly, which can be nice. But the main advantage comes with multiple inheritance, where all sorts of fun stuff can happen. See the standard docs on super if you haven't already.

Note that the syntax changed in Python 3.0: you can just say super().__init__() instead of super(ChildB, self).__init__() which IMO is quite a bit nicer. The standard docs also refer to a guide to using super() which is quite explanatory.

Why does super() inherits the wrong class?

You need to keep in mind that the MRO is not just a simple follow-the-leader. It creates a graph-like structure and resolves identical inheritances to avoid duplication. In your first example, you have created a diamond inheritance.

   A
/ \
B C
\ /
D

The MRO seeks resolution of methods from the first level up (the immediate parent classes), from left to right (B then C), then the next level up, from left to right (just A here), and so on.

In this case, because you have both B and C inheriting from A, the top level resolves to a single A and creates the diamond diagram above.

Let's look at your second example:

class D(B, C):
def __init__(self):
print("This is class D")
B.__init__(self)
C.__init__(self)

By implementing it this way, you are effectively by-passing the MRO. You have taken the inheritance diamond and made it an inheritance olive fork that looks like this:

 A   A
| |
B C
\ /
D

Because of this you are calling the initialization of A twice, which does not need to occur. In long inheritance chains or complicated initialization routines, this is very inefficient.

[python]: confused by super()

super() returns an instance of the base class, so self gets implicitly passed to __init__() like in any other method call.

With regards to your second question, that's correct. Calling super() without an instance as the second argument returns a reference to the class itself, not an instance constructed from your subclass instance.

Python's Multiple Inheritance: Picking which super() to call

That's not what super() is for. Super basically picks one (or all) of its parents in a specific order. If you only want to call a single parent's method, do this

class ASDF(ASDF1, ASDF2, ASDF3):
def __init__(self):
ASDF2.__init__(self)


Related Topics



Leave a reply



Submit