Python's Super and MRO

by Andrew Campbell
  python

Using Python’s super() with multiple inheritance can get confusing at times, but hopefully after this walkthrough you will see how simple, yet brilliant, this feature is.

Two important things you should take away:

  • The MRO (Method Resolution Order) linearizes your related classes creating what is referred to as an ancestor tree.
  • Then starting with the lowest child class of the ancestor tree, super() calls the next class in line. (Super does not just point to the parent class)
class Person(object):
    def send_email(self):
        print "EMAIL SENT"

class Student(Person):
    def send_email(self):
        print "ABOUT TO SEND"
        super(Student, self).send_email()

me = Student()
me.send_email()
# ABOUT TO SEND
# EMAIL SENT
print Student.__mro__
# (<class '__main__.Student'>, <class '__main__.Person'>, <type 'object'>)

As you can see, this simple example of single inheritance prints the outputs as expected. Now lets say we want to mock Person for testing purposes. Without touching our original code, we can utilize super() and manipulate the MRO to make our own unit test.

class MockPerson(Person):
    def send_email(self):
        print "MOCK"

class TestStudent(Student, MockPerson):
    def send_email(self):
        print "TESTING"
        super(TestStudent, self).send_email()

test = TestStudent()
test.send_email()
# TESTING
# ABOUT TO SEND
# MOCK
print TestStudent.__mro__
# (<class '__main__.TestStudent'>, <class '__main__.Student'>, <class '__main__.MockPerson'>, <class '__main__.Person'>, <type 'object'>)

Now we created a MockPerson class that overrides Person’s send_email() method. Then we created a child class, TestStudent, altering the MRO by injecting MockPerson. So the new MRO for TestStudent is TestStudent–>Student–>MockPerson–>Person (the term for this is dependency injection). And voilà! Person’s send_email() method never gets called :).