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 -
- super is a builtin function, not an attribute.
- Every type (class) in Python has an
__mro__
attribute, that stores the method resolution order of that particular instance. - 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.
- At the starting point of super calls, the object is of the type of the Derived class (say DC).
- 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). - 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. - Rinse wash repeat till all methods are found and called.
Explanation for your example
MRO: D,B,C,A,object
super(D, self).__init__()
is called. isinstance(self, D) => TrueSearch for next method in the MRO in classes to the right of D.
B.__init__
found and called
B.__init__
callssuper(B, self).__init__()
.isinstance(self, B) => False
isinstance(self, D) => TrueThus, 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.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
Python: Start New Command Prompt on Windows and Wait for It Finish/Exit
Attributeerror: Can Only Use .Dt Accessor with Datetimelike Values
Using the Multiprocessing Module for Cluster Computing
Activating Anaconda Environment in VScode
How to Execute a Group by ... Count or Sum in Django Orm
Python Float to Int Conversion
Download File Using Partial Download (Http)
Return SQL Table as JSON in Python
Pandas: Valueerror: Cannot Convert Float Nan to Integer
Pygame Window Not Responding After Few Seconds
Django Rest Framework Post Nested Objects
How to Print a List with Integers Without the Brackets, Commas and No Quotes
Ioerror: [Errno 22] Invalid Mode ('R') or Filename: 'C:\\Python27\Test.Txt'
A Queryset by Aggregate Field Value