×

Polymorphism in OOPS – Python

Polymorphism means the ability to represent an object in many forms using a single interface. Polymorphism is derived from two different words poly which means many, morphs means many forms.

Polymorphism in Python allows us to use the operators, methods, constructors in different forms and perform overloading and overriding on them. The different types of polymorphism are:-

  • Overloading
  • Overriding

Overloading in Python

Based on the values we pass if an object changes its behavior and returns a different outcome then it is known as overloading. Thus we can use the same operator or method for different purposes. Overloading in Python is of three types:-

  • Operator Overloading
  • Method Overloading
  • Constructor Overloading

Operator Overloading

If we can use the same operator for multiple purposes then it is known as Operator Overloading. Python does support Operator Overloading.

For example, we can perform operator overloading on the “+” operator. If we pass two numbers then the “+” operator returns the result of addition performed between the numbers.

The same operator if we pass two strings then it concatenates them and returns the result as a single string.

# Addition Operator "+"

3 + 5   -> 8
24 + 21 -> 45


# Concatenation Operator "+"

"Python" + "tutorial"  -> Pythontutorial

Similarly, If we pass the multiplication operator( * ) between two numbers then it returns the product of the multiplication.

The same ” * ” operator if we pass it between an integer and a string then it performs the string repetition operation.

# Mulitplication Operator "*"

4 * 5 -> 20

# String Repetition Operator "*"

3 * "Python" -> PythonPythonPython

Thus the same operators performing different operators based on the values we pass is called Operator Overloading.

For a complete tutorial on Operator Overloading please click the link OPERATOR OVERLOADING IN PYTHON

__str__() method

The object contains the members of the class. The object reference is used to point to the object of the class and using the object reference we access the members it has stored in it.

If we try to print the object reference the values returned will be in a string format which contains the memory address of the location in which the object is stored.

class car:
  def __init__(self, fuel_litres, fuel_price):
    self.fuel_price = fuel_price
    self.fuel_litres = fuel_litres
obj = car(20, 100)
print(obj)

# Output
<__main__.car object at 0x7f9b79da9790>

But to print the values present in the object this is not needed. This is the reason we need to define a method that can print the values present in the object.

Basically, when we print the object_reference of an object then the internally __str__() method is called. So if we define this method explicitly from our side then the values stored in an object can be returned.

class car:
  def __init__(self, fuel_litres, fuel_price):
    self.fuel_price = fuel_price
    self.fuel_litres = fuel_litres
  def __str__(self):
    return f"Fuel price: {self.fuel_price} \nFuel litres: {self.fuel_litres}"
obj = car(20, 100)
print(obj)

# Output
Fuel price: 100 
Fuel litres: 20

Method Overloading

If there are two or more methods in a class with the same name but a different number of arguments then it is known as Method overloading. In Python, Method Overloading is not supported.

Even though method overloading is not supported in Python, if we try to declare multiple methods with the same name but different arguments then the bottom-most or last method is considered and executed.

In the below example, even though we are overloading the method with multiple arguments the bottom-most method is considered as m1() and is executed.

class employee:
    def m1(self):
        print("First method")
    def m1(self, b):
        print("Second method")
    def m1(self, c, d ):
        print("Third method")
    def m1(self):
        print("Fourth method")
      

obj = employee()
obj.m1()

# Output
Fourth method

The reason behind this is that Python is a Dynamically Typed Language. Basically dynamically typed languages do not support method overloading. Dynamically typed means in python, we don’t explicitly determine the data type of the variable.

Instead of declaring multiple methods with a different number of arguments each time, we use the variable argument concept. These variable arguments are stored in a tuple and can be accessed by iterating over a for loop.

class employee:
    def m1(self, *a):
        for i in a:
          print(i)
obj = employee()
obj.m1("hello", 20, "python")

# Output
hello
20
python

Constructor Overloading

If we define more than one constructor with the same name but with different arguments then it is known as constructor overloading. Python does not support constructor overloading.

Similar to method overloading, python also doesn’t support constructor overloading due to dynamic typing. In case if we try to declare multiple constructors with the same name but variable arguments, then the bottom-most constructor is considered.

class employee:
    def __init__(self):
      print("constructor_1")
    def __init__(self, a, b):
      print("constructor_2")
     
obj = employee(10, 20)

# Output
constructor_2

Instead of declaring multiple constructors with a different number of arguments each time, we use the variable argument concept. These variable arguments are stored in a tuple and can be accessed by iterating over a for loop.

class employee:
    def __init__(self, *a):
        print("constructor method")
obj = employee(20, 30, 50, 60)
obj = employee("hello", 20, "python")

# Output
constructor method
constructor method

Overriding in Python

In inheritance, the child class can access all the members of the parent class. But if the child class is not satisfied by the features of the parent class then it can re-define few members and use them. This is known as overriding.

In Python, we can override methods and constructors.

Method Overriding in Python

To override a method in the parent class we need to redefine the same method in the child class. So when the method is called from the object of the child class the new method will be called due to Method Resolution Order(MRO).

In the below example, class A is inherited by class B. But in class B we are overriding the method() by redefining it. Now if we call the method(), the child class method is called because it is overriding the method in the parent class.

class A:
    def method(self):
        print("Class A method")
class B(A):
    def method(self):
        print("Class A method overrided")
        print("new features added")

obj = B()
obj.method()

# Output
Class A method overrided
new features added

If we still want to use the parent class method then we can use super() function to load the method into child class and make use of both the methods.

class A:
    def method(self):
        print("Class A method")
class B(A):
    def method(self):
        super().method()
        print("Class A method overrided")
        print("new features added")

obj = B()
obj.method()

# Output
Class A method
Class A method overrided
new features added

Constructor Overriding in Python

To override a constructor in the parent class we need to redefine the same constructor in the child class. So when the constructor is called during the object creation of the child class it overrides the method in the parent class.

In the below example, class A is inherited by class B. But in class B we are overriding the __init__() by redefining it. Now if we create the object of the child class, the new constructor will be executed by overriding the constructor in the parent class.

class A:
  def __init__(self):
    print("Class A")
class B(A):
  def __init__(self):
    print("Constructor override")
    print("new features added")

obj = B()

# Output
Constructor override
new features added

Similarly, If we still want to use the parent class constructor then we can use super() function to load the constructor into child class and make use of both constructors.

class A:
  def __init__(self):
    print("Class A")
class B(A):
  def __init__(self):
    super().__init__()
    print("Constructor override")
    print("new features added")

obj = B()

# output
Class A
Constructor override
new features added