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 > 0:
            index -= 1
        return list.__getitem__(self, index)

    def __setitem__(self, index, value):
        if index == 0:
            raise IndexError
        if index > 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

 

Abstract Base Classes/Methods – Object Oriented Programming

Abstract classes, in short, are classes that are supposed to be inherited or subclassed, rather than instantiated.

Through Abstract Classes, we can enforce a blueprint on the subclasses that inherit the Abstract Class. This means that Abstract classes can be used to define a set of methods that must be implemented by it subclasses.

Abstract classes are used when working on large projects where classes have to be inherited, and need to strictly follow certain blueprints.

Python supports Abstract Classes via the module abc from version 2.6. Using the abc module, its pretty straight forward to implement an Abstract Class.

Example 0:

import abc

class My_ABC_Class(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def set_val(self, val):
        return

    @abc.abstractmethod
    def get_val(self):
        return

# Abstract Base Class defined above ^^^

# Custom class inheriting from the above Abstract Base Class, below

class MyClass(My_ABC_Class):

    def set_val(self, input):
        self.val = input

    def get_val(self):
        print("\nCalling the get_val() method")
        print("I'm part of the Abstract Methods defined in My_ABC_Class()")
        return self.val

    def hello(self):
        print("\nCalling the hello() method")
        print("I'm *not* part of the Abstract Methods defined in My_ABC_Class()")

my_class = MyClass()

my_class.set_val(10)
print(my_class.get_val())
my_class.hello()

In the code above, set_val() and get_val() are both abstract methods defined in the Abstract Class My_ABC_Class(). Hence it should be implemented in the child class inheriting from My_ABC_Class().

In the child class MyClass() , we have to strictly define the abstract classes defined in the Parent class. But the child class is free to implement other methods of their own. The hello() method is one such.

This will print :

# python abstractclasses-1.py

Calling the get_val() method
I'm part of the Abstract Methods defined in My_ABC_Class()
10

Calling the hello() method
I'm *not* part of the Abstract Methods defined in My_ABC_Class()

The code gets executed properly even if the  hello() method is not an abstract method.

Let’s check what happens if we don’t implement a method marked as an abstract method, in the child class.

Example 1:

import abc

class My_ABC_Class(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def set_val(self, val):
        return

    @abc.abstractmethod
    def get_val(self):
        return

# Abstract Base Class defined above ^^^

# Custom class inheriting from the above Abstract Base Class, below

class MyClass(My_ABC_Class):

    def set_val(self, input):
        self.val = input

    def hello(self):
        print("\nCalling the hello() method")
        print("I'm *not* part of the Abstract Methods defined in My_ABC_Class()")

my_class = MyClass()

my_class.set_val(10)
print(my_class.get_val())
my_class.hello()

Example 1 is the same as Example 0 except we don’t have the get_val() method defined in the child class.

This means that we’re breaking the rule of abstraction. Let’s see what happens:

# python abstractclasses-2.py
Traceback (most recent call last):
  File "abstractclasses-2.py", line 50, in
    my_class = MyClass()
TypeError: Can't instantiate abstract class MyClass with abstract methods get_val

The traceback clearly states that the child class MyClass() cannot be instantiated since it does not implement the Abstract methods defined in it’s Parent class.

We mentioned that an Abstract class is supposed to be inherited rather than instantiated. What happens if we try instantiating an Abstract class?

Let’s use the same example, this time we’re instantiating the Abstract class though.

Example 2:

import abc

class My_ABC_Class(object):
    __metaclass__ = abc.ABCMeta

    @abc.abstractmethod
    def set_val(self, val):
        return

    @abc.abstractmethod
        def get_val(self):
            return

# Abstract Base Class defined above ^^^

# Custom class inheriting from the above Abstract Base Class, below

class MyClass(My_ABC_Class):

    def set_val(self, input):
        self.val = input

    def hello(self):
        print("\nCalling the hello() method")
        print("I'm *not* part of the Abstract Methods defined in My_ABC_Class()")

my_class = My_ABC_Class()    # <- Instantiating the Abstract Class

my_class.set_val(10)
print(my_class.get_val())
my_class.hello()

What does this output?

# python abstractclasses-3.py
Traceback (most recent call last):
    File "abstractclasses-3.py", line 54, in <module>
       my_class = My_ABC_Class()
TypeError: Can't instantiate abstract class My_ABC_Class with abstract methods get_val, set_val

As expected, the Python interpreter says that it can’t instantiate the abstract class My_ABC_Class.

Takeaway: 

  1. An Abstract Class is supposed to be inherited, not instantiated.
  2. The Abstraction nomenclature is applied on the methods within a Class.
  3. The abstraction is enforced on methods which are marked with the decorator @abstractmethod or @abc.abstractmethod, depending on how you imported the module, from abc import abstractmethod or import abc.
  4. It is not mandatory to have all the methods defined as abstract methods, in an Abstract Class.
  5. Subclasses/Child classes are enforced to define the methods which are marked with @abstractmethod in the Parent class.
  6. Subclasses are free to create methods of their own, other than the abstract methods enforced by the Parent class.

Reference:

  1. https://pymotw.com/2/abc/
  2. Python beyond the basics – Object Oriented Programming