Introduction

One of Python's key features is its support for object-oriented programming (OOP) concepts such as polymorphism. Polymorphism is a technique that allows different objects to be treated in a similar way as they share a common interface. This means that developers can write code that works with a variety of different data types in a single, unified way. This tutorial will explore the concept of polymorphism in Python, how it works and how it can be used in real-world applications.

Table of Contents :

  • Polymorphism in Python
  • Method Overriding
  • Method Overloading
    • using default arguments or 
    • using variadic functions
    • using keyword arguments
    • using @dispatch decorator
  • Duck Typing
  • Operator Overloading

Polymorphism in Python :

  • Polymorphism is the ability of an object to take on different forms. 
  • Python is an object-oriented programming language. 
  • This means that every value in Python is an object, and every object has a type
  • For example, 
    • the integer 1 is an object with the type int, 
    • the string "hello" is an object with the type str, and 
    • the list [1, 2, 3] is an object with the type list. 
  • Objects can have different types depending on their value. 
  • In Python, this means that an object can be of more than one type. 
  • For example, the integer 1 is also an object with the type float. This is because Python automatically converts integers to floats when they are used in a float context. 
  • Similarly, the string "hello" is also an object with the type list. This is because Python automatically converts strings to lists when they are used in a list context. 
  • Polymorphism is a powerful feature of Python that allows for great flexibility in how objects are used.
  • Polymorphism is the ability for objects of different classes to be treated as if they are of the same class.
  • In Python, we can implement polymorphism through 
    • method overriding and 
    • method overloading.

Method Overriding:

  • Method overriding allows a sub-class to provide a different implementation of a method that is already defined in its super-class.
  • It is achieved by defining a method with the same name in the sub-class as in the super-class.
  • The sub-class method will be called instead of the super-class method when a method is called on an instance of the sub-class.
  • Code Sample :

class Animal:
    def speak(self):
        print("Animal speaks")


class Dog(Animal):
    def speak(self):
        print("Dog barks")


class Cat(Animal):
    def speak(self):
        print("Cat meows")


a = Animal()
a.speak()

d = Dog()
d.speak()

c = Cat()
c.speak()


# Output
# Animal speaks
# Dog barks
# Cat meows



Method Overloading :

  • Method overloading allows methods with the same name to have different signatures or parameters.
  • Python does not support method overloading by default.
  • We can define two or more methods with same name in python but it will use the method defined at last.
  • But we can achieve method overloading in python by using any of the following methods :
    • using default arguments or 
    • using variadic functions (*args) 
    • using keyword arguments (**kwargs) 
    • using @dispatch decorator

Method Overloading using default arguments :

  • We can give a default value to some of the arguments of the function that we want to overload.
  • Because of the default value we can pass different number of arguments while calling the function, thus overloading the function.
  • Code Sample :

class Calculator:
    def add(self, x, y, z=0):
        return x + y + z


c = Calculator()

sum_2 = c.add(6, 7)
sum_3 = c.add(2, 3, 4)

print(f"The sum of three numbers = {sum_3}")
print(f"The sum of two numbers = {sum_2}")


# Output
# The sum of three numbers = 9
# The sum of two numbers = 13



Method Overloading using variadic functions :

  • Functions with *args as one of the parameter are known as variadic functions.
  • We know that variadic functions can take variable number of arguments.
  • This feature of variadic functions to take variable number of arguments can be used to overload functions.
  • Code Sample :

class Calculator:
    def add(self, x, y, *args):
        l = len(args)
        if l > 0:
            return x + y + args[0]
        else:
            return x + y


c = Calculator()

sum_2 = c.add(6, 7)
sum_3 = c.add(2, 3, 4)

print(f"The sum of three numbers = {sum_3}")
print(f"The sum of two numbers = {sum_2}")


# Output
# The sum of three numbers = 9
# The sum of two numbers = 13


Method Overloading using keyword arguments :

  • We can use keyword arguments to overload functions in python.
  • We know that functions with keyword arguments can take variable number of arguments.
  • This method of passing variable number of arguments can be used to overload functions.
  • Code Sample :

class Calculator:
    def add(self, x, y, **kwargs):
        if 'z' in kwargs:
            return x + y + kwargs["z"]
        else:
            return x + y


c = Calculator()

sum_2 = c.add(x=6, y=7)
sum_3 = c.add(x=2, y=3, z=4)

print(f"The sum of three numbers = {sum_3}")
print(f"The sum of two numbers = {sum_2}")


# Output
# The sum of three numbers = 9
# The sum of two numbers = 13



Method Overloading using dispatch decorator :

  • to overload functions in python we can use the  @dispatch  decorator.
  • To use the  @dispatch  decorator we first need to install a module called  multipledispatch 
  • We can install the   multipledispatch   module using pip :   pip3 install multipledispatch 
  • Dispatcher internally creates an object that saves different implementations of the function.
  • At runtime it matches the number and type of parameters and invokes the appropriate method.
  • Code Sample :

from multipledispatch import dispatch


@dispatch(int, int)
def add(num_1, num_2):
    result = num_1 + num_2
    return result


@dispatch(int, int, int)
def add(num_1, num_2, num_3):
    result = num_1 + num_2 + num_3
    return result


@dispatch(float, float, float)
def add(num_1, num_2, num_3):
    result = num_1 + num_2 + num_3
    return result


# calling product method with 2 arguments
res = add(5, 4)
print(f"The sum of two numbers = {res}")

# calling product method with 3 arguments but all int
res = add(5, 4, 2)
print(f"The sum of three numbers = {res}")

# calling product method with 3 arguments but all float
res = add(5.1, 4.31, 2.6)
print(f"The sum of three float numbers = {res}")


# Output
# The sum of two numbers = 9
# The sum of three numbers = 11
# The sum of three float numbers = 12.01



Duck Typing :

  • Duck typing is a concept in Python where the type or class of an object is less important than the methods it defines.
  • It allows different types of objects to be treated similarly if they have similar methods or attributes.
  • Code Sample :

class Duck:
    def quack(self):
        print("Quack quack")


class Person:
    def quack(self):
        print("I can't quack like a duck, but I can mimic it")


def duck_quack(obj):
    obj.quack()


d = Duck()
p = Person()

# Both the Duck and Person objects can be passed to the duck_quack() function
duck_quack(d)
duck_quack(p)


# Output
# Quack quack
# I can't quack like a duck, but I can mimic it



Operator Overloading :

  • Operator overloading allows the operators such as +, -, *, /, ==, <, >, etc. to be treated differently depending on the data type of operands.
  • It is achieved by defining special functions that Python uses for these operators, such as 
    • __add__
    •  __sub__
    • __mul__
    • __eq__
    • __lt__
    • __gt__ etc.
  • Code Sample :

class Vector:
   def __init__(self, x, y):
       self.x = x
       self.y = y
       
   def __add__(self, other):
       return Vector(self.x + other.x, self.y + other.y)

v1 = Vector(2, 3)
v2 = Vector(4, 5)
v3 = v1 + v2
print(v3.x, v3.y)    

# Output: 
# 6 8

Prev. Tutorial : Encapsulation

Next Tutorial : Inheritance