Overall
Assuming everything descends from object
(you are on your own if it doesn't), Python computes a method resolution order (MRO) based on your class inheritance tree. The MRO satisfies 3 properties:
- Children of a class come before their parents
- Left parents come before right parents
- A class only appears once in the MRO
If no such ordering exists, Python errors. The inner workings of this is a C3 Linerization of the classes ancestry. Read all about it here: https://www.python.org/download/releases/2.3/mro/
When a method is called, the first occurrence of that method in the MRO is the one that is called. Any class that doesn't implement that method is skipped. Any call to super
within that method will call the next occurrence of that method in the MRO. Consequently, it matters both what order you place classes in inheritance, and where you put the calls to super
in the methods.
Note that you can see the MRO in python by using the __mro__
method.
Examples
All of the following examples have a diamond inheritance of classes like so:
Parent
/ \
/ \
Left Right
\ /
\ /
Child
The MRO is:
- Child
- Left
- Right
- Parent
You can test this by calling Child.__mro__
, which returns:
(__main__.Child, __main__.Left, __main__.Right, __main__.Parent, object)
With super
first in each method
class Parent(object):
def __init__(self):
super(Parent, self).__init__()
print("parent")
class Left(Parent):
def __init__(self):
super(Left, self).__init__()
print("left")
class Right(Parent):
def __init__(self):
super(Right, self).__init__()
print("right")
class Child(Left, Right):
def __init__(self):
super(Child, self).__init__()
print("child")
Child()
outputs:
parent
right
left
child
With super
last in each method
class Parent(object):
def __init__(self):
print("parent")
super(Parent, self).__init__()
class Left(Parent):
def __init__(self):
print("left")
super(Left, self).__init__()
class Right(Parent):
def __init__(self):
print("right")
super(Right, self).__init__()
class Child(Left, Right):
def __init__(self):
print("child")
super(Child, self).__init__()
Child()
outputs:
child
left
right
parent
When not all classes call super
Inheritance order matters most if not all classes in the chain of inheritance call super
. For example, if Left
doesn't call super, then the method on Right
and Parent
are never called:
class Parent(object):
def __init__(self):
print("parent")
super(Parent, self).__init__()
class Left(Parent):
def __init__(self):
print("left")
class Right(Parent):
def __init__(self):
print("right")
super(Right, self).__init__()
class Child(Left, Right):
def __init__(self):
print("child")
super(Child, self).__init__()
Child()
outputs:
child
left
Alternatively, if Right
doesn't call super
, Parent
is still skipped:
class Parent(object):
def __init__(self):
print("parent")
super(Parent, self).__init__()
class Left(Parent):
def __init__(self):
print("left")
super(Left, self).__init__()
class Right(Parent):
def __init__(self):
print("right")
class Child(Left, Right):
def __init__(self):
print("child")
super(Child, self).__init__()
Here, Child()
outputs:
child
left
right
Calling a method on a particular parent
If you want to access the method of a particular parent class, you should reference that class directly rather than using super. Super is about following the chain of inheritance, not getting to a specific class's method.
Here's how to reference a particular parent's method:
class Parent(object):
def __init__(self):
super(Parent, self).__init__()
print("parent")
class Left(Parent):
def __init__(self):
super(Left, self).__init__()
print("left")
class Right(Parent):
def __init__(self):
super(Right, self).__init__()
print("right")
class Child(Left, Right):
def __init__(self):
Parent.__init__(self)
print("child")
In this case, Child()
outputs:
parent
child