The super() Method and Dreaded Diamond in Python
This post deals with a special case of inheritance that is commonly discussed in several programming languages, i.e. the Dreaded Diamond. It further clarifies the advantage in usage of the super() method with the dreaded diamond in python with the help of a program.
Prerequisites: Basics of Multiple-Inheritance
What is super()?
According to the Python documentation,
super(), returns a proxy object that delegates method calls to a parent or sibling class of type. This is useful for accessing inherited methods that have been overridden in a class.
In simple words, it is used to refer to its immediate super-class or parent-class. It is considered a better method to access data members and member functions of the base class, rather than using the base class name itself.
Syntax: super(<own_class_name>,self).<function_to_be_called>(<args...>) #Python 2.7.x super().<function_to_be_called(<args...>) #Python 3.x
NOTE: All the programs hereafter are for Python 2.7.x only
The Dreaded Diamond in Python
The dreaded diamond is a special case of inheritance wherein, two classes inherit from the same base class and another class inherits from both these derived classes. That is, classes B and C inherit from class A. Class D in turn, inherits from both B and C.
We will consider this special case to understand the usage of the super() method in Python and how it is an improvement of the old-style classes. But before that, we introduce Method Resolution Order (MRO)
Method Resolution Order (MRO)
MRO is simply the order that a programming language follows to find an attribute (or data member) or a member function that has been used/called somewhere in a program. When there is an inheritance hierarchy, there is a specific order in which the called member is looked for.
When there is an inheritance hierarchy and a derived class defines a method with the same name as a function in its base class, there is overriding. The function defined in the derived class hides its definition in the base class. Hence in general, when a member function is called, the definition in the derived class is used. For more details, refer: method overriding in Python
Old-Style and New-Style Python Classes
Python initially considered classes and types as different concepts. These are called old-style classes. Later on, an update was made for various reasons such as to make classes a user-defined data type and so on. These new-style classes have a slightly different MRO. They also have some new methods and attributes. For more details: refer the documentation
For this topic, there is one major syntactic difference between the two types of classes. In new-style classes, the base class at the top of the inheritance hierarchy must inherit from “object”, a built-in class pre-defined in the python libraries.
- super() Method and Function Overriding in Python
- Difference between static method and class method in Python
NOTE: Only the new-style classes can use the super() method.
Program to Illustrate super() with Dreaded Diamond in Python
Consider the following code
class A(): def __init__(self): print 'A' def foo(self): print "Function in A" class B(A): def __init__(self): print 'B' A.__init__(self) class C(A): def __init__(self): print 'C' A.__init__(self) def foo(self): print "Function in C" class D(B,C): def __init__(self): print 'D' B.__init__(self) C.__init__(self) obj = D() obj.foo()
This is an old-style implementation of the dreaded diamond case. Notice the call to the __init__() of the base classes in the derived classes. They all use the name of the base class to call the function.
Also, note that the function foo() defined in the base class A is overridden by the derived class C.
The resulting MRO for an object of class D will be D, B, A, C, A. Notice that class A occurs twice because it is a base class to both B as well as C. Hence when obj.foo() needs to be found, it is looked for in the order D, B, A, C, A. Since it will be found in A first, the function definition in class A is executed.
The output is as follows:
Now consider the following code using the new-style classes that highlights the usage of the super() method with the dreaded diamond,
class A(object): def __init__(self): print 'A' def foo(self): print "Function in A" class B(A): def __init__(self): print 'B' super(B,self).__init__() class C(A): def __init__(self): print 'C' super(C,self).__init__() def foo(self): print "Function in C" class D(B,C): def __init__(self): print 'D' super(D,self).__init__() print D.__mro__ obj = D() obj.foo()
Notice the use of super() to invoke the __init__() functions of the base classes. Further, note that in class D, super needs to be used only once to invoke the __init__() of its base classes, even though there are two base classes. super() automatically invokes all base classes of class D in the right order specified by the new-style classes’ MRO. However, in the previous example, the __init__() for both classes had to be called separately.
NOTE: The in-built method attribute __mro__ is defined for new-style classes. It displays the resolution order for the calling class (see output).
Here, the MRO for an object of class D will be D, B, C, A. Hence, when the call to function to foo() is made, C is encountered first in the MRO. Hence the definition in class C gets executed.
The output is as follows:
Why the super() method is Better?
The reason why the super() method is better is simply that, in an inheritance, the derived classes must be able to update the functions and data members of the base class. This is, in fact, one of the uses of inheritance. It is often used to update the features defined in the base class by simply overriding its members without disturbing the structure of the base class. This ensures that the changes being made do not reflect in other derived classes of the same base class. Other derived classes may inherit the same base class for a different purpose and might need to use the same old definition. Hence this way, changes in definition (or redefinition) are made only in the relevant derived class.
From the outputs, it is clear that in the old-style classes, the whole purpose of overriding is defeated. The definition in the penultimate base class – A, is executed. The updated definition in class C is ignored. Whereas in the new-style classes, the newly updated definition in class C is executed.
Hence the usage of the super() method with the dreaded diamond is a better option. It is also convenient and advantageous in several other types of inheritance implementations.