Python's super() Function: A Comprehensive Guide

A Deeper Dive into Its Functionality and Applications

In partnership with

TL;DR: The super() function in Python is often misunderstood as simply calling the method of the direct parent class. However, it actually follows the Method Resolution Order (MRO), which is based on the C3 Linearization algorithm. This means that super() doesn't just call methods from the immediate parent but instead looks at the next class in the MRO. This is particularly important in multiple inheritance scenarios, such as the "diamond problem," where super() helps avoid duplicate method calls from the same class. Using super() without arguments automatically follows MRO, while specifying a class and instance (e.g., super(B, self).show()) can bypass certain classes in the MRO. Understanding this behavior is crucial for effective use of super() in complex inheritance structures.

I used to think that Python's super() function was straightforward, simply calling the method of the parent class. However, as I delved deeper, I discovered that my understanding was incomplete. I was missing a crucial aspect of how super() truly works.

Let me share what went wrong and what I learned along the way.

Mistakes I made

Initially, my understanding of super() was limited to this basic concept:

class Parent:
    def greet(self):
        print("Parent")

class Child(Parent):
    def greet(self):
        super().greet()
        print("Child")

obj = Child()
obj.greet()

I expected that super().greet() would directly call the method from the parent class. However, the actual behavior revealed a more complex mechanism.

When I ran the code, I observed that super().greet() indeed called the parent class's method, but this was just the beginning of understanding how super() truly functions. The key insight came later, when I explored its behavior in more complex inheritance scenarios.

I thought super() will always call the method from the direct parent. But it isn't true.

Reality About super() (That I Didn't Know)

I didn't realize that super() follows the Method Resolution Order (MRO) rather than simply calling the direct parent class. This revelation has added complexity to my understanding. Let me illustrate what I found confusing:

In multiple inheritance scenarios, super() doesn't just call the method from the immediate parent; it searches for methods based on the MRO. This means that the order in which methods are called can be different from what one might initially expect, especially in cases like the "diamond problem" where multiple inheritance is involved.

I was expecting this:

  1. D.show() will call super().show(), which I thought direct parent (B)

  2. B.show() will call super().show(), which I thought direct parent (A)

  3. C.show() will never get called.

Find out why 1M+ professionals read Superhuman AI daily.

In 2 years you will be working for AI

Or an AI will be working for you

Here's how you can future-proof yourself:

  1. Join the Superhuman AI newsletter โ€“ read by 1M+ people at top companies

  2. Master AI tools, tutorials, and news in just 3 minutes a day

  3. Become 10X more productive using AI

Join 1,000,000+ pros at companies like Google, Meta, and Amazon that are using AI to get ahead.

The reality about MRO

But I got this output:

Hereโ€ฆ C.show() running before A.show(). I was shocked to get this result.

MRO (Method Resolution Order)

Python's Method Resolution Order (MRO) is governed by the C3 Linearization algorithm, which determines the sequence in which base classes are searched when resolving methods or attributes.

Key Principles of MRO

  1. C3 Linearization Rules

    • Children precede parents: Subclasses appear before their superclasses.

    • Order preservation: The order of inheritance declared in a class is respected.

    • Monotonicity: No class is visited more than once, and the MRO of a class must be a consistent extension of the MROs of its parents.

  2. How Python Determines MRO
    Python constructs the MRO by:

    1. Collecting all classes in the inheritance hierarchy.

    2. Applying C3 to linearize them into a unique order.

    3. Resolving conflicts (e.g., inconsistent hierarchies) by raising a TypeError.

Here's a breakdown of how it works:

print(D.mro())  # This will check the MRO of class D

The output is:

So, when we callsuper().show() inside D.show():

It didn't look at the direct parent (B). It just looks at the next class in MRO. That's the reason that C.show() is called before A.show().

This is what I learned

  1. super() follows MRO. It does not call the methods from the direct parents only.

  2. super() work in diamond inheritance. It helps us to avoid duplicate method calls from the same class in multiple inheritance.

  3. If we call super() without any arguments, then it will use MRO. When we do like this:

super().show()

Python will automatically follow MRO.

But if we do like this:

super(B, self).show()

Python will skip to A, thus, it will ignore C.

Conclusion

Python's super() function is more nuanced than initially meets the eye. It doesn't simply call methods from the direct parent class but instead follows the Method Resolution Order (MRO), which is crucial for handling complex inheritance scenarios like the "diamond problem." By understanding how super() works with MRO, developers can write more robust and efficient code that avoids duplicate method calls and ensures that methods are called in the correct order. This insight not only clarifies common misconceptions about super() but also highlights its importance in designing well-structured class hierarchies.