5 Best Ways to Implement Polymorphism in Python

πŸ’‘ Problem Formulation: Polymorphism is an essential concept in object-oriented programming that allows for methods and functions to interact with many different data types through a unified interface. In Python, it allows us to define methods in the child class with the same name as defined in their parent class. In this article, we explore how to effectively implement polymorphism with examples that take a class or function and provide different outputs based on the object or arguments passed.

Method 1: Using Inheritance and Method Overriding

One common way to achieve polymorphism in Python is through inheritance and method overriding. This allows subclass methods to provide a specific implementation of a method that is already defined in its superclass. The specific method called depends on the data type of the object invoking it.

Here’s an example:

class Bird:
    def make_sound(self):
        return "Some generic bird sound"

class Sparrow(Bird):
    def make_sound(self):
        return "Chirp chirp"

class Ostrich(Bird):
    def make_sound(self):
        return "Boom boom"

bird = Bird()
sparrow = Sparrow()
ostrich = Ostrich()

print(bird.make_sound())     # Output: Some generic bird sound
print(sparrow.make_sound())  # Output: Chirp chirp
print(ostrich.make_sound())  # Output: Boom boom

This code snippet illustrates the concept of polymorphism through method overriding. The make_sound method in each bird type subclass (Sparrow and Ostrich) overrides the default implementation in the base Bird class.

Method 2: Using Duck Typing

In Python, ‘duck typing’ is a way to apply polymorphism by design, adhering to the principle: “If it looks like a duck and quacks like a duck, it’s a duck”. This means that the suitability of an object to be used for a specific purpose is determined by the presence of certain methods and properties, rather than the actual type of the object.

Here’s an example:

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

class Person:
    def quack(self):
        return "I'm impersonating a duck!"

def make_it_quack(ducky):
    print(ducky.quack())

duck = Duck()
john = Person()

make_it_quack(duck)   # Output: Quack quack
make_it_quack(john)   # Output: I'm impersonating a duck!

This code demonstrates polymorphism through duck typing. The function make_it_quack can accept any object as long as it has a quack method that can be invoked, regardless of the object’s type.

Method 3: Using Abstract Base Classes

Abstract Base Classes (ABCs) in Python provide a way to define blueprints for other classes. They facilitate polymorphism by allowing subclasses to provide concrete implementations of the abstract methods defined within.

Here’s an example:

from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def speak(self):
        pass

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

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

dog = Dog()
cat = Cat()

print(dog.speak())  # Output: Woof!
print(cat.speak())  # Output: Meow!

The Animal class is an abstract class that uses the @abstractmethod decorator to define the blueprint method speak. The subclasses Dog and Cat provide their respective implementations of this blueprint.

Method 4: Using Function Overloading with Single Dispatch

Function overloading in Python can be achieved using the functools.singledispatch decorator. This allows for polymorphic behavior in functions, enabling them to act differently based on the type of the first argument they receive.

Here’s an example:

from functools import singledispatch

@singledispatch
def speak(obj):
    return f"This is a generic object."

@speak.register
def _(obj: Dog):
    return f"Woof!"

@speak.register
def _(obj: Cat):
    return f"Meow!"

print(speak(Dog()))   # Output: Woof!
print(speak(Cat()))   # Output: Meow!

Here, the speak function is decorated with @singledispatch and can be overloaded with different implementations for different types using the @speak.register decorator.

Bonus One-Liner Method 5: Using the Operator Module

Polymorphic behavior can be utilized in a one-liner using the operator module, which provides a set of efficient functions corresponding to the intrinsic operators of Python.

Here’s an example:

import operator

add = operator.add
print(add(1, 2))  # Output: 3
print(add("hello ", "world"))  # Output: hello world

This snippet uses the add function from the operator module to add numbers as well as concatenate strings, showcasing polymorphism in a simple yet effective manner.

Summary/Discussion

    Method 1: Inheritance and Method Overriding. Ideal for class hierarchies. Requires class methods to be overridden. It may cause confusion if the method hierarchy is complex. Method 2: Duck Typing. Pythonic and flexible. It relies on runtime behavior and can lead to less predictable code. Method 3: Abstract Base Classes. Promotes structure and readability. Requires the definition of an abstract class and abstract methods. Method 4: Function Overloading with Single Dispatch. Useful for overloading functions based on a single parameter type. Limited to the first argument’s type without additional modules. Method 5: Operator Module. Quick and easy for simple cases. Limited to built-in operations and doesn’t provide full control for custom behaviors.