6.5. Inheritance MRO
MRO - Method Resolution Order
Inheritance Diamond
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
User parents come before Admin 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/
object
|
Account
/ \
/ \
User Admin
\ /
\ /
MyAccount
Thus, in examples above, it is:
MyAccount
User
Admin
Account
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.
6.5.1. Show MRO
cls.mro()
cls.__mro__
Note that you can see the MRO in python by using the .mro()
method:
>>> class User:
... pass
>>> User.mro()
[<class '__main__.User'>, <class 'object'>]
>>> User.__mro__
(<class '__main__.User'>, <class 'object'>)
6.5.2. Small Diamond
object
|
Account
/ \
User Admin
\ /
MyAccount
>>> class Account:
... pass
>>>
>>> class User(Account):
... pass
>>>
>>> class Admin(Account):
... pass
>>>
>>> class MyAccount(User, Admin):
... pass
>>> MyAccount.mro()
[<class '__main__.MyAccount'>,
<class '__main__.User'>,
<class '__main__.Admin'>,
<class '__main__.Account'>,
<class 'object'>]
6.5.3. Large Diamond
object
|
Account
/ \
User Admin
| |
SuperUser SuperAdmin
\ /
MyAccount
>>> class Account:
... pass
>>>
>>> class User(Account):
... pass
>>>
>>> class SuperUser(User):
... pass
>>>
>>> class Admin(Account):
... pass
>>>
>>> class SuperAdmin(Admin):
... pass
>>>
>>> class MyAccount(SuperUser, SuperAdmin):
... pass
>>> MyAccount.mro()
[<class '__main__.MyAccount'>,
<class '__main__.SuperUser'>,
<class '__main__.User'>,
<class '__main__.SuperAdmin'>,
<class '__main__.Admin'>,
<class '__main__.Account'>,
<class 'object'>]
6.5.4. Case Study - 1
Linear Inheritance
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
super().login()
class Admin(User):
def login(self):
print('Admin')
super().login()
class MyAccount(Admin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>, <class '__main__.Admin'>, <class '__main__.User'>, <class '__main__.Account'>, <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# Admin
# User
# Account
6.5.5. Case Study - 2
Multiple Inheritance
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
class Admin(Account):
def login(self):
print('Admin')
class MyAccount(User, Admin):
def login(self):
print('MyAccount')
MyAccount.mro()
# [<class '__main__.MyAccount'>, <class '__main__.User'>, <class '__main__.Admin'>, <class '__main__.Account'>, <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
class Admin(Account):
def login(self):
print('Admin')
class MyAccount(User, Admin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>, <class '__main__.User'>, <class '__main__.Admin'>, <class '__main__.Account'>, <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# User
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
super().login()
class Admin(Account):
def login(self):
print('Admin')
class MyAccount(User, Admin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>, <class '__main__.User'>, <class '__main__.Admin'>, <class '__main__.Account'>, <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# User
# Admin
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
super().login()
class Admin(Account):
def login(self):
print('Admin')
super().login()
class MyAccount(User, Admin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>, <class '__main__.User'>, <class '__main__.Admin'>, <class '__main__.Account'>, <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# User
# Admin
# Account
6.5.6. Case Study - 3
Multiple and Linear Inheritance
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
class SuperUser(User):
def login(self):
print('SuperUser')
class Admin(Account):
def login(self):
print('Admin')
class SuperAdmin(Admin):
def login(self):
print('SuperAdmin')
class MyAccount(SuperUser, SuperAdmin):
def login(self):
print('MyAccount')
MyAccount.mro()
# [<class '__main__.MyAccount'>,
# <class '__main__.SuperUser'>,
# <class '__main__.User'>,
# <class '__main__.SuperAdmin'>,
# <class '__main__.Admin'>,
# <class '__main__.Account'>,
# <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
class SuperUser(User):
def login(self):
print('SuperUser')
class Admin(Account):
def login(self):
print('Admin')
class SuperAdmin(Admin):
def login(self):
print('SuperAdmin')
class MyAccount(SuperUser, SuperAdmin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>,
# <class '__main__.SuperUser'>,
# <class '__main__.User'>,
# <class '__main__.SuperAdmin'>,
# <class '__main__.Admin'>,
# <class '__main__.Account'>,
# <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# SuperUser
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
class SuperUser(User):
def login(self):
print('SuperUser')
super().login()
class Admin(Account):
def login(self):
print('Admin')
class SuperAdmin(Admin):
def login(self):
print('SuperAdmin')
class MyAccount(SuperUser, SuperAdmin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>,
# <class '__main__.SuperUser'>,
# <class '__main__.User'>,
# <class '__main__.SuperAdmin'>,
# <class '__main__.Admin'>,
# <class '__main__.Account'>,
# <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# SuperUser
# User
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
super().login()
class SuperUser(User):
def login(self):
print('SuperUser')
super().login()
class Admin(Account):
def login(self):
print('Admin')
class SuperAdmin(Admin):
def login(self):
print('SuperAdmin')
class MyAccount(SuperUser, SuperAdmin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>,
# <class '__main__.SuperUser'>,
# <class '__main__.User'>,
# <class '__main__.SuperAdmin'>,
# <class '__main__.Admin'>,
# <class '__main__.Account'>,
# <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# SuperUser
# User
# SuperAdmin
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
super().login()
class SuperUser(User):
def login(self):
print('SuperUser')
super().login()
class Admin(Account):
def login(self):
print('Admin')
class SuperAdmin(Admin):
def login(self):
print('SuperAdmin')
super().login()
class MyAccount(SuperUser, SuperAdmin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>,
# <class '__main__.SuperUser'>,
# <class '__main__.User'>,
# <class '__main__.SuperAdmin'>,
# <class '__main__.Admin'>,
# <class '__main__.Account'>,
# <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# SuperUser
# User
# SuperAdmin
# Admin
class Account:
def login(self):
print('Account')
class User(Account):
def login(self):
print('User')
super().login()
class SuperUser(User):
def login(self):
print('SuperUser')
super().login()
class Admin(Account):
def login(self):
print('Admin')
super().login()
class SuperAdmin(Admin):
def login(self):
print('SuperAdmin')
super().login()
class MyAccount(SuperUser, SuperAdmin):
def login(self):
print('MyAccount')
super().login()
MyAccount.mro()
# [<class '__main__.MyAccount'>,
# <class '__main__.SuperUser'>,
# <class '__main__.User'>,
# <class '__main__.SuperAdmin'>,
# <class '__main__.Admin'>,
# <class '__main__.Account'>,
# <class 'object'>]
me = MyAccount()
me.login()
# MyAccount
# SuperUser
# User
# SuperAdmin
# Admin
# Account
6.5.7. Algorithm
>>> class Base:...
>>>
>>> class A(Base): ...
>>> class B(Base): ...
>>>
>>> class C(A): ...
>>> class D(A): ...
>>> class F(B): ...
>>> class G(B): ...
>>>
>>> class E(C, D): ...
>>> class H(F, G): ...
>>>
>>> class Final(E, H): ...
Note that a class X will be visited only if all its sub-classes, which inherit from it, have been visited(i.e., you should never visit a class that has an arrow coming into it from a class below that you have not yet visited).
Here, note that after visiting class C
, D
is visited although
C
and D
DO NOT have is a relationship between them (but both have
with A
). This is where super()
differs from single inheritance.
>>> Final.mro()
[<class '__main__.Final'>,
<class '__main__.E'>,
<class '__main__.C'>,
<class '__main__.D'>,
<class '__main__.A'>,
<class '__main__.H'>,
<class '__main__.F'>,
<class '__main__.G'>,
<class '__main__.B'>,
<class '__main__.Base'>,
<class 'object'>]
Consider a slightly more complicated example:
>>> class Base:...
>>>
>>> class A(Base): ...
>>> class B(Base): ...
>>>
>>> class C(A): ...
>>> class D(A): ...
>>> class F(B): ...
>>> class G(B): ...
>>>
>>> class E(C, D, F): ...
>>> class H(D, F, G): ...
>>>
>>> class Final(E, H): ...
In this case we proceed from I
to E
to C
. The next step up
would be A
, but we have yet to visit D
, a subclass of A
.
We cannot visit D
, however, because we have yet to visit H
,
a subclass of D
. The leaves H
as the next class to visit.
Remember, we attempt to go up in hierarchy, if possible, so we visit
its leftmost superclass, D
. After D
we visit A
, but we cannot
go up to object because we have yet to visit F
, G
, and B
.
These classes, in order, round out the MRO for I
.
Note that no class can appear more than once in MRO.
This is how super()
looks up in the hierarchy of inheritance.
>>> Final.mro()
[<class '__main__.Final'>,
<class '__main__.E'>,
<class '__main__.C'>,
<class '__main__.H'>,
<class '__main__.D'>,
<class '__main__.A'>,
<class '__main__.F'>,
<class '__main__.G'>,
<class '__main__.B'>,
<class '__main__.Base'>,
<class 'object'>]
6.5.8. Inconsistent MRO
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.
>>> class Account:
... pass
...
>>> class User(Account):
... pass
...
>>> class Admin(Account, User):
... pass
...
Traceback (most recent call last):
TypeError: Cannot create a consistent method resolution order (MRO) for bases Account, User
Should Admin
's MRO be [Account, User]
or [User, Account]
?
There's no obvious expectation, and Python will raise an error.
However if we change the order of inheritance, it works:
>>> class Account:
... pass
...
>>> class User(Account):
... pass
...
>>> class Admin(User, Account):
... pass
>>> Admin.mro()
[<class '__main__.Admin'>, <class '__main__.User'>, <class '__main__.Account'>, <class 'object'>]
6.5.9. Further Reading
van Rossum, G. Method Resolution Order. Year: 2010. Retrieved: 2022-07-13. URL: http://python-history.blogspot.com/2010/06/method-resolution-order.html
Hettinger R. Super considered super!. PyCon 2015. Year: 2020. Retrieved: 2022-07-13. URL: https://www.youtube.com/watch?v=EiOglTERPEo