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'>]
../../_images/inheritance-mro-diamond-small.png

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'>]
../../_images/inheritance-mro-diamond-large.png

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): ...
../../_images/inheritance-mro-example-1.png

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): ...
../../_images/inheritance-mro-example-2.png

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'>]
../../_images/inheritance-mro-inconsistent.png

6.5.9. Further Reading

6.5.10. References

6.5.11. Assignments