Polymorphism in Python

Polymorphism is one of the four core principles of Object-Oriented Programming (OOP), along with encapsulation, inheritance, and abstraction. The term "polymorphism" is derived from Greek words meaning "many forms." In the context of Python and OOP, polymorphism refers to the ability of different classes to respond to the same method call in a way that is specific to their own implementation.

Polymorphism allows objects of different classes to be treated as objects of a common superclass. It provides the flexibility to call the same method on different objects, and each object can respond in its own way.

In this article, we will explore the concept of polymorphism, discuss its types, and look at various examples to see how it works in Python.


Types of Polymorphism in Python

There are two main types of polymorphism in Python:

  1. Method Overriding (Runtime Polymorphism)
  2. Method Overloading (Compile-Time Polymorphism)

Let’s look at each type in detail.


1. Method Overriding (Runtime Polymorphism)

Method overriding is the most common form of polymorphism in object-oriented languages like Python. It allows a subclass to provide its own implementation of a method that is already defined in its superclass.

In method overriding, the child class redefines a method of the parent class with its own specific functionality, but the method signature remains the same.

Example of Method Overriding

class Animal:
    def speak(self):
        print("Animal makes a sound")

class Dog(Animal):
    def speak(self):
        print("Woof!")

class Cat(Animal):
    def speak(self):
        print("Meow!")

# Creating objects of Dog and Cat classes
dog = Dog()
cat = Cat()

# Calling the same method on both objects
dog.speak()  # Output: Woof!
cat.speak()  # Output: Meow!

Explanation:

  • The Animal class defines a method speak().
  • Both Dog and Cat classes inherit from Animal and override the speak() method with their own implementation.
  • When dog.speak() is called, the speak() method from the Dog class is invoked, and similarly, when cat.speak() is called, the method from the Cat class is invoked.

This is an example of runtime polymorphism because the method that gets executed is determined at runtime based on the type of object being referenced.


2. Method Overloading (Compile-Time Polymorphism)

Method overloading occurs when two or more methods in the same class have the same name but different parameters (different number or types of arguments). While Python doesn’t support true method overloading like other programming languages (e.g., Java or C++), you can achieve a similar effect by using default arguments or variable-length arguments (*args, **kwargs).

Example of Method Overloading using Default Arguments

class Calculator:
    def add(self, a, b, c=0):  # Default argument for the third parameter
        return a + b + c

# Creating an object of Calculator
calc = Calculator()

# Calling the method with two arguments
print(calc.add(3, 5))  # Output: 8

# Calling the method with three arguments
print(calc.add(3, 5, 7))  # Output: 15

Explanation:

  • In this example, the method add() has a default value for the third parameter (c=0), which allows the method to handle both two-argument and three-argument calls.
  • This is an example of how Python mimics method overloading by using default arguments.

Polymorphism with Interfaces

Python doesn’t have formal interfaces like some other OOP languages (e.g., Java). However, Python supports duck typing, which means that an object’s suitability is determined by its methods and properties, rather than the type of the object itself. This can be seen as an informal form of polymorphism, where different objects can be used interchangeably if they implement the same method or behavior.

Example of Duck Typing in Python

class Bird:
    def fly(self):
        print("Flying in the sky!")

class Airplane:
    def fly(self):
        print("Flying in the air!")

# Function that expects an object that can fly
def make_it_fly(flyable_object):
    flyable_object.fly()

# Creating objects of Bird and Airplane classes
bird = Bird()
airplane = Airplane()

# Passing different objects to the same function
make_it_fly(bird)      # Output: Flying in the sky!
make_it_fly(airplane)  # Output: Flying in the air!

Explanation:

  • Both Bird and Airplane classes implement the fly() method.
  • The make_it_fly() function accepts any object that has a fly() method, demonstrating the duck typing principle.
  • Even though Bird and Airplane are unrelated classes, they can be treated similarly because they both have the fly() method.

Advantages of Polymorphism in Python

  1. Code Reusability: Polymorphism allows you to write code that works with objects of multiple types, reducing the need for duplicate code.

  2. Simplified Code Maintenance: By using polymorphism, you can write more flexible and maintainable code. For instance, adding new classes that implement the same methods won’t require modifying existing code.

  3. Enhanced Flexibility: Polymorphism enables objects to be used interchangeably, which increases the flexibility and adaptability of your code.


Real-World Example of Polymorphism in Python

Let’s say you’re building a system to handle different types of payment methods (e.g., credit card, PayPal, and Bitcoin). You can use polymorphism to define a common pay() method across all payment types, while each payment method provides its own implementation of the payment process.

Example: Payment System using Polymorphism

class CreditCard:
    def pay(self, amount):
        print(f"Paid ${amount} using Credit Card.")

class PayPal:
    def pay(self, amount):
        print(f"Paid ${amount} using PayPal.")

class Bitcoin:
    def pay(self, amount):
        print(f"Paid ${amount} using Bitcoin.")

# Function to process payment
def process_payment(payment_method, amount):
    payment_method.pay(amount)

# Creating objects of different payment methods
credit_card = CreditCard()
paypal = PayPal()
bitcoin = Bitcoin()

# Processing payments using different methods
process_payment(credit_card, 100)  # Output: Paid $100 using Credit Card.
process_payment(paypal, 200)       # Output: Paid $200 using PayPal.
process_payment(bitcoin, 300)      # Output: Paid $300 using Bitcoin.

Explanation:

  • The CreditCard, PayPal, and Bitcoin classes each implement the pay() method.
  • The process_payment() function uses polymorphism to process payments with different payment methods.
  • This allows you to easily add more payment methods in the future without changing the existing code.