Attribute searching is the core concept for Python classes. The searching order is important as it determines which class's attributes are used ( the one first searched is used ).
In Python2's classic classes, the searching method is called DFLR ( Depth First, Left to Right ). Let's take an example to make it easier to understand.
Classes B and C inherit from A, while class D inherits from B and C.
Figure 1
The DFLR order is D, B, A, C, A.
Please note that A appears twice on the list.
In Python3, every class's most top parent must be 'object'. So if a class has more than one parent class, 'object' is always listed multiple times on DFLR.
This will cause two issues:
To solve these issues, Python3 replaces DFLR with MRO. MRO stands for Method Resolution Order. Despite its name, MRO is used for any attributes besides methods.
MRO is a simple improvement to DFLR. It just removes all but the last occurrence of duplicated classes in the DFLR list. Let's still use the example in Figure 1.
The DFLR list is D, B, A, C, A.
The corresponding MRO list is D, B, A, C, A.
MRO can be easily accessed by class.__mro__ or class.mro().
>>> class A: pass
...
>>> class B(A): pass
...
>>> class C(A): pass
...
>>> class D(B,C): pass
...
>>> D.__mro__
(
>>> D.mro()
[
super() is a builtin provided by Python3 for users to utilize MRO. Usually, python progammers explicitly call parent class's method with class name.
$ cat super1.py
class A:
def __init__(self):
print('A.__init__()')
class B(A):
def __init__(self):
print("B.__init__()")
A.__init__(self)
x = B()
$ python3 super1.py
B.__init__()
A.__init__()
This seems a little bit noisy. If class A changes its name, we have to modify two places in class B accordingly.
Like Java, Python3 provides a super() builtin for this.
$ cat super2.py
class A:
def __init__(self):
print('A.__init__()')
class B(A):
def __init__(self):
print("B.__init__()")
super().__init__()
x = B()
Everything looks perfect now.
But super() is not that simple. It dynamically calculates the invoked method based on MRO, which means the super() here does NOT always invoke class A. A real example would be much easier for us to understand what this means.
$ cat super3.py
#!/usr/bin/python3
class A:
def __init__(self):
print('A.__init__()')
class B(A):
def __init__(self):
print("B.__init__()")
super().__init__()
class C(A):
def __init__(self):
print("C.__init__()")
class D(B,C): pass
obj = D()
$ python3 super3.py
B.__init__()
C.__init__()
Here we can see that the "super()" in class B actually invoked class C rather than its real superclass A.
"super()" here calculates the invoked class based on two facts, the class D's MRO and current class B.
class D's MRO is D, B, C, A, object.
super() will invoke the class after B in D's MRO, which is C.
super() actually accepts parameters as
super(current_class, instance_object)
Inside it
$ cat super4.py
class A:
def __init__(self):
print('A.__init__()')
class B(A):
def __init__(self):
print("B.__init__()")
super( __class__, self).__init__()
class C(A):
def __init__(self):
print("C.__init__()")
class D(B,C): pass
obj = D()
super(B, obj).__init__()
$ python3 super4.py
B.__init__()
C.__init__()
C.__init__()
super() seems good at first look, but it can cause lots of issues. MRO order may not be what you really want when invoking a method.
More debates about super() can be found in the section "The super Built-in Function: For better or worse" of Learning Python (5th Edition).