Understanding Python's super(): Unraveling its True Purpose
Written on
Chapter 1: Unpacking super() in Python
What exactly is the super() function in Python? While many believe it simply calls a parent class method during inheritance, there's more complexity beneath the surface. Let's delve deeper into this concept and gain a comprehensive understanding of super().
Basic Inheritance
In typical inheritance scenarios, there exists one child class and one parent class. For example, when invoking super().walk() in the child class, it indeed calls the walk() method from its parent class as anticipated. This may lead to the impression that super() directly invokes the parent class method.
class Human:
def walk(self):
print("Human walks", self)
class Friend(Human):
def walk(self):
print("Friend walks", self)
super().walk()
friend = Friend()
friend.walk()
Output:
Friend walks <__main__.Friend object at 0x105142e50>
Human walks <__main__.Friend object at 0x105142e50>
Multiple Classes
Consider a scenario where Bird is a direct parent of ColorfulBird, yet it lacks a sing method. Will this lead to an error? Surprisingly, it will not. The super() function traverses the inheritance hierarchy to locate the method, demonstrating its ability to search methodically until it finds a match.
class Animal:
def sing(self):
print("Animal does cool sounds: sound1 sound2 sound3")
class Bird(Animal):
pass
class ColorfulBird(Bird):
def sing(self):
print("Colorful bird sings: Lalalalalala", self)
super().sing()
bird = ColorfulBird()
bird.sing()
Output:
Colorful bird sings: Lalalalalala <__main__.ColorfulBird object at 0x...>
Animal does cool sounds: sound1 sound2 sound3
The Diamond Problem
Let’s explore a more complex inheritance structure. We have four classes: A, B, C, and Root. Here, C inherits from both A and B, with each class containing a method invoked by super(). This raises questions about method precedence: which method gets called first? Will the Root method be executed multiple times, or will the inheritance hierarchy allow us to bypass A and B directly to the Root method?
Python utilizes a rule known as Method Resolution Order (MRO) to handle this situation. MRO flattens the diamond-shaped inheritance structure into a linear sequence, which can be viewed using the __mro__ attribute.
The MRO is computed via the C3 linearization algorithm, which systematically determines the order. Essentially, the MRO of a class C is derived by merging C with the linearization of its parent classes and their respective lists in the order they are inherited.
class Root:
def walk(self):
print("Root walk", self)
class A(Root):
def walk(self):
print("A walk", self)
super().walk()
class B(Root):
def walk(self):
print("B walk", self)
super().walk()
class C(B, A):
def walk(self):
print("C walk", self)
super().walk()
class D(C):
def walk(self):
print("D walk", self)
super().walk()
d = D()
d.walk()
Output:
D walk <__main__.D object at 0x...>
C walk <__main__.D object at 0x...>
B walk <__main__.D object at 0x...>
A walk <__main__.D object at 0x...>
Root walk <__main__.D object at 0x...>
More on __mro__
To observe the order of method calls, you can examine C.__mro__. However, it won't always yield a consistent result. The classes in the hierarchy must align correctly; if they don’t, you may encounter errors.
print(C.__mro__)
If you attempt to introduce a conflicting class, an error will arise. This occurs because Python aims to maintain the order of classes as they appear in the inheritance list while ensuring each class precedes its superclasses.
class D(A, C):
def walk(self):
print("D walk", self)
super().walk()
# TypeError: Cannot create a consistent method resolution order (MRO) for bases A, C
Super: What Are You?
The super() function can be viewed as a proxy class, allowing for method delegation.
class A:
def func(self):
print("A: ", self)
class B(A):
def func(self):
print("B: ", self)
sup = super()
print(sup)
sup.func()
b = B()
b.func()
Output:
B: <__main__.B object at 0x...>
<__main__.B object at 0x...>
A: <__main__.B object at 0x...>
Implementing a Simple Proxy
Let’s create a straightforward proxy class that allows us full control over the methods we apply to an object.
class BasicProxy:
def __init__(self, obj):
self.obj = obj
def __getattr__(self, item):
print(item)
return getattr(self.obj, item)
numbers = [1, 2, 3, 4, 5, 6, 7]
proxy = BasicProxy(numbers)
proxy.append(8)
print(proxy == numbers)
print(proxy.obj == numbers)
Output:
append
False
True
Creating a Fake Super
We can also simulate a version of super() to understand its functionality better. This imitation requires manually specifying the parent class.
class FakeSuper:
def __init__(self, cls, obj):
self.obj = obj
self.cls = cls
def __getattr__(self, item):
attr = getattr(self.cls, item)
if hasattr(type(attr), "__get__"):
attr = attr.__get__(self.obj)
print(attr)
return attr
class A:
def func(self):
print("A: ", self)
class B(A):
def func(self):
print("B: ", self)
FakeSuper(A, self).func()
b = B()
b.func()
Output:
B: <__main__.B object at 0x...>
A: <__main__.B object at 0x...>
The FakeSuper class acts as a proxy with enhanced functionality for methods, allowing for a deeper understanding of how the super() function operates.
If you found this exploration insightful and wish to join our expanding community, be sure to follow us as we embark on this knowledge journey together. Your thoughts and comments are always welcomed!
In Plain English 🚀
Thank you for being part of the In Plain English community! Before you leave, don't forget to follow us on X, LinkedIn, YouTube, Discord, and subscribe to our newsletter for more content.
A simple explanation of super() in Python - YouTube. This video clarifies the use of super() in Python inheritance.
Python super function - YouTube. An in-depth look at how the super function operates in Python.