Inheritance and super() – Object Oriented Programming

super() is a feature through which inherited methods can be accessed, which has been overridden in a class. It can also help with the MRO lookup order in case of multiple inheritance. This may not be obvious first, but a few examples should help to drive the point home.

Inheritance and method overloading was discussed in a previous post, where we saw how inherited methods can be overloaded or enhanced in the child classes.

In many scenarios, it’s needed to overload an inherited method, but also call the actual method defined in the Parent class.

Let’s start off with a simple example based on Inheritance, and build from there.

Example 0:

class MyClass(object):

    def func(self):
        print("I'm being called from the Parent class!")

class ChildClass(MyClass):
    pass

my_instance_1 = ChildClass()
my_instance_1.func()

This outputs:

In [18]: %run /tmp/super-1.py
I'm being called from the Parent class

In Example 0, we have two classes, MyClass and ChildClass. The latter inherits from the former, and the parent class MyClass has a method named func defined.

Since ChildClass inherits from MyClass, the child class has access to the methods defined in the parent class. An instance is created my_instance_2, for ChildClass.

Calling my_instance_1.func() will print the statement from the Parent class, due to the inheritance.

Building up on the first example:

Example 1:

class MyClass(object):

    def func(self):
        print("I'm being called from the Parent class")

class ChildClass(MyClass):

    def func(self):
        print("I'm being called from the Child class")

my_instance_1 = MyClass()
my_instance_2 = ChildClass()

my_instance_1.func()
my_instance_2.func()

This outputs:

In [19]: %run /tmp/super-1.py
I'm being called from the Parent class
I'm being called from the Child class

This example has a slight difference, both the child class as well as the parent class have the same method defined, ie.. func. In this scenario, the parent class’ method is overridden by the child class method.

ie.. if we call the func() method from the instance of ChildClass, it need not go a fetch the method from its Parent class, since it’s already defined locally.

NOTE: This is due to the Method Resolution Order, discussed in an earlier post.

But what if there is a scenario that warranties the need for specifically calling methods defined in the Parent class, from the instance of a child class?

ie.. How to call the methods defined in the Parent class, through the instance of the Child class, even if the Parent class method is overloaded in the Child class?

In such a case, the inbuilt function super() can be used. Let’s add to the previous example.

Example 2:

class MyClass(object):

    def func(self):
        print("I'm being called from the Parent class")

class ChildClass(MyClass):

    def func(self):
        print("I'm actually being called from the Child class")
        print("But...")
        # Calling the `func()` method from the Parent class.
        super(ChildClass, self).func()

my_instance_2 = ChildClass()
my_instance_2.func()

This outputs:

In [21]: %run /tmp/super-1.py
I'm actually being called from the Child class
But...
I'm being called from the Parent class

How is the code structured?

  1. We have two classes MyClass and ChildClass.
  2. The latter is inheriting from the former.
  3. Both classes have a method named func
  4. The child class ChildClass is instantiated as my_instance_2
  5. The func method is called from the instance.

How does the code work?

  1. When the func method is called, the interpreter searches it using the Method Resolution Order, and find the method defined in the class ChildClass.
  2. Since it finds the method in the child class, it executes it, and prints the string “I’m actually being called from the Child class”, as well “But…”
  3. The next statement is super which calls the method func defined in the parent class of ChildClass
  4. Since the control is now passed onto the func method in the Parent class via super, the corresponding print() statement is printed to stdout.

Example 2 can also be re-written as :

class MyClass(object):

    def func(self):
        print("I'm being called from the Parent class")

class ChildClass(MyClass):

    def func(self):
        print("I'm actually being called from the Child class")
        print("But...")
        # Calling the `func()` method from the Parent class.
        # super(ChildClass, self).func()
        MyClass.func(self)  # Call the method directly via Parent class

my_instance_2 = ChildClass()
my_instance_2.func()

 

NOTE: The example above uses the Parent class directly to access it’s method. Even though it works, it is not the best way to do it since the code is tied to the Parent class name. If the Parent class name changes, the child/sub class code has to be changed as well. 

Let’s see another example for  super() . This is from our previous article on Inheritance and method overloading.

Example 3:

import abc

class MyClass(object):

    __metaclass__ = abc.ABCMeta

    def my_set_val(self, value):
        self.value = value

    def my_get_val(self):
        return self.value

    @abc.abstractmethod
    def print_doc(self):
        return

class MyChildClass(MyClass):

    def my_set_val(self, value):
        if not isinstance(value, int):
            value = 0
        super(MyChildClass, self).my_set_val(self)

    def print_doc(self):
        print("Documentation for MyChild Class")

my_instance = MyChildClass()
my_instance.my_set_val(100)
print(my_instance.my_get_val())
print(my_instance.print_doc())

The code is already discussed here. The my_set_val method is defined in both the child class as well as the parent class.

We overload the my_set_val method defined in the parent class, in the child class. But after enhancing/overloading it, we call the my_set_val method specifically from the Parent class using super() and thus enhance it.

Takeaway:

  1. super() helps to specifically call the Parent class method which has been overridden in the child class, from the child class.
  2. The super() in-built function can be used to call/refer the Parent class without explicitly naming them. This helps in situations where the Parent class name may change. Hence, super() helps in avoiding strong ties with class names and increases maintainability.
  3. super() helps the most when there are multiple inheritance happening, and the MRO ends up being complex. In case you need to call a method from a specific parent class, use super().
  4. There are multiple ways to call a method from a Parent class.
    1. <Parent-Class>.<method>
    2. super(<ChildClass>, self).<method>
    3. super().<method>

References:

  1. https://docs.python.org/2/library/functions.html#super
  2. https://rhettinger.wordpress.com/2011/05/26/super-considered-super/
  3. https://stackoverflow.com/questions/222877/how-to-use-super-in-python

Inheritance and Method overloading – Object Oriented Programming

Inheritance is a usual theme in Object Oriented Programming. Because of Inheritance, the functions/methods defined in parent classes can be called in Child classes which enables code reuse, and several other features. In this article, we try to understand some of those features that come up with Inheritance.

We’ve discussed Abstract Methods in an earlier post, which is a feature part of Inheritance, and can be applied on child classes that inherits from a Parent class.

E the methods which are inherited can also be seen as another feature or possibility in Inheritance. In many cases, it’s required to override or specialize the methods inherited from the Parent class. This is of course possible, and is called as ‘Method Overloading’.

Consider the two classes and its methods defined below:

Example 0:

import abc

class MyClass(object):

    __metaclass__ = abc.ABCMeta

    def __init__(self):
        pass

    def my_set_method(self, value):
        self.value = value

    def my_get_method(self):
        return self.value

    @abc.abstractmethod
    def printdoc(self):
        return

class MyChildClass(MyClass):

    def my_set_method(self, value):
        if not isinstance(value, int):
            value = 0
        super(MyChildClass, self).my_set_method(self)

    def printdoc(self):
        print("\nDocumentation for MyChildClass()")

instance_1 = MyChildClass()
instance_1.my_set_method(10)
print(instance_1.my_get_method())
instance_1.printdoc()

 

We have two classes, the parent class being MyClass and the child class being MyChildClass.

MyClass has three methods defined.

  • my_set_method()
  • my_get_method()
  • printdoc()

The printdoc() method is an Abstract method, and hence should be implemented in the Child class as a mandatory method.

The child class MyChildClass inherits from MyClass and has access to all it’s methods.

Normally, we can just go ahead and use the methods defined in MyClass , in MyChildClass. But there can be situations when we want to improve or build upon the methods inherited. As said earlier, this is called Method Overloading.

MyChildClass extends the parent’s my_set_method() function by it’s own implementation. In this example, it does an additional check to understand if the input value is an int or not, and then calls the my_set_method() of it’s parent class using super. Hence, this method in the child class extends the functionality prior calling method in the parent. A post on super is set for a later time.

Even though this is a trivial example, it helps to understand how the features inherited from other classes can be extended or improved upon via method overloading.

The my_get_method() is not overridden in the child class but still called from the instance, as instance_1.my_get_method(). We’re using it as it is available via Inheritance. Since it’s defined in the parent class, it works in the child class’ instance when called, even if not overridden.

The printdoc() method is an abstract method and hence is mandatory to be implemented in the child class, and can be overridden with what we choose to do.

Inheritance is possible from python builtins, and can be overridden as well. Let’s check out another example:

Example 1:

class MyList(list):

    def __getitem__(self, index):
        if index == 0:
            raise IndexError
        if index &gt; 0:
            index -= 1
        return list.__getitem__(self, index)

    def __setitem__(self, index, value):
        if index == 0:
            raise IndexError
        if index &gt; 0:
            index -= 1
        list.__setitem__(self, index, value)

x = MyList(['a', 'b', 'c'])
print(x)
print("-" * 10)

x.append('d')
print(x)
print("-" * 10)

x.__setitem__(4, 'e')
print(x)
print("-" * 10)

print(x[1])
print(x.__getitem__(1))
print("-" * 10)

print(x[4])
print(x.__getitem__(4))

This outputs:

['a', 'b', 'c']
----------
['a', 'b', 'c', 'd']
----------
['a', 'b', 'c', 'e']
----------
a
a
----------
e
e

How does the code work?

The class MyList() inherits from the builtin list. Because of the inheritance, we can use list’s available magic methods such as __getitem__() , __setitem__() etc..

NOTE: In order to see the available methods in list, use dir(list).

  1. We create two functions/methods named `__getitem__()` and `__setitem__()` to override the inherited methods.
  2. Within these functions/methods, we set our own conditions.
  3. Wie later call the builtin methods directly within these functions, using
    1. list.__getitem__()
    2. list.__setitem__()
  4. We create an instance named x from MyList().
  5. We understand that
    1. x[1] and x.__getitem__(1) are same.
    2. x[4, 'e'] and x.__setitem__(4, 'e') are same.
    3. x.append(f) is same as x.__setitem__(<n>, f) where <n> is the element to the extreme right which the python interpreter iterates and find on its own.

Hence, in Inheritance, child classes can:

  • Inherit from parent classes and use those methods.
    • Parent classes can either be user-defined classes or buitins like list , dict etc..
  • Override (or Overload) an inherited method.
  • Extend an inherited method in its own way.
  • Implement an Abstract method the parent class requires.

Reference:

  1. Python beyond the basics – Object Oriented Programming

 

Method Resolution Order – Object Oriented Programming

Method Resolution Order or ‘MRO’ in short, denotes the way a programming language resolves a method or attribute. This post looks into how Method Resolution Order works, using Python.

Python supports classes inheriting from other classes. The class being inherited is called the Parent/Super class, while the class that inherits is called the Child/Sub class.

While inheriting from another class, the interpreter needs a way to resolve the methods that are being called via an instance. Hence a method resolution order is needed.

Example 0:


class A(object):
    def my_func(self):
        print("Doing this in class A")

class B(A):
    def my_func(self):
        print("Doing this in class B")

my_instance = B()
my_instance.my_func()

Structure:

  1. We’ve two classes,  class A and class B.
  2. Instantiate class B as my_instance.
  3. Call the my_func() method through the my_instance instance.

Where is the method fetched from? From class B or class A?

How does the code work?

This should be pretty obvious, the answer would be class B. But why is it being called from class B and not from class A?

Answer : The Method Resolution Order [MRO].

To understand this in depth, let’s check another example:

Example 1:

class A(object):
    def my_func(self):
        print("Doing this in Class A")

class B(A):
    pass

class C(object):
    def my_func(self):
        print("Doing this in Class C")

class D(B, C):
    pass

my_instance = D()
my_instance.my_func()

Structure:

  1. Four classes, class A, B, C, and D.
  2. Class D inherits from both B and C
  3. Class B inherits from A.
  4. Class A and C doesn’t inherit from any super classes, but from the object base class due to being new-style classes.
  5. Class A and class C both have a method/function named my_func().
  6. Class D is instantiated through my_instance

If we were to call the method my_func() through the my_instance() instance, which class would it be called from? Would it be from class A or class C?

How does the code work?

This won’t be as obvious as Example 0. 

  1. The instance my_instance() is created from class D.
  2. Since class Dinherits from both class B and C, the python interpreter searches for the method my_func() in both of these classes.
  3. The intrepreter finds that class B inherits from class A, and class C doesn’t have any super classes other than the default object class.
  4. Class A and class C both has the method named my_func(), and hence has to be called from one of these.
  5. Python follows a depth-first lookup order and hence ends up calling the method from class A.

Following the depth-first Method Resolution Order, the lookup would be in the order :

Class D -> Class B -> Class C

Let’s check another example, which can be a bit more complex.

Example 2:

class A(object):
    def my_func(self):
        print("Doing this in A")

class B(A):
    pass

class C(A):
    def my_func(self):
        print("doing this in C")

class D(B, C):
    pass

my_instance = D()
my_instance.my_func()

Structure:

  1. Four classes, class A, B, C, and D
  2. Class D inherits from both B and C
  3. Class B inherits from class A.
  4. Class C inherits from class A.
  5. Class A inherits from the default base class object.

This sort of inheritance is called the Diamond Inheritance or the Deadly Diamond of death and looks like the following:

 

220px-Diamond_inheritance.svg

Image courtsey : Wikipedia

How does the code work?

Following the depth-first Method Resolution Order, the lookup would be in the order :

Class D -> Class B -> Class A -> Class C -> Class A

In order to avoid ambiguity while doing a lookup for a method where multiple classes are inherited and involved, the MRO lookup has changed slightly from Python 2.3 onwards.

It still goes for the depth-first order, but if the occurrence of a class happens multiple times in the MRO path, it removes the initial occurrence and keeps the latter.

Hence, the look up order in Example 2 becomes:

Class D -> Class B -> Class C -> Class A.


NOTE: Python provides a method for a class to lookup the Method Resolution Order. Let’s recheck Example 2 using that.

class A(object):
    def my_func(self):
        print("Calling this from A")

class B(A):
    pass

class C(A):
    def my_func(self):
        print("\nCalling this from C")

class D(B, C):
    pass

my_instance = D()
my_instance.my_func()

print("\nPrint the Method Resolution Order")
print(D.mro())
print(D.__bases__)

This should print:

# python /tmp/Example-2.py

Calling this from C

Print the Method Resolution Order
class '__main__.D', class '__main__.B', class '__main__.C', class '__main__.A', type 'object'

(, )

Takeaway:

  1. Python follows a depth-first order for resolving methods and attributes.
  2. In case of multiple inheritances where the methods happen to occur more than once, python omits the first occurrence of a class in the Method Resolution Order.
  3. The <class>.mro()methods helps to understand the Medthod Resolution Order.
  4. The `__bases__` and `__base__` magic methods help to understand the Base/Parent classes of a Sub/Child class.

References:

  1. https://en.wikipedia.org/wiki/Multiple_inheritance