Daily Tech Brief

Top startup stories in your inbox

Subscribe Free

© 2026 rakrisi Daily

Polymorphism - Flexible Interfaces

Polymorphism: Flexible Interfaces

Welcome to Polymorphism! Think of it as shape-shifting - the same method name can behave differently depending on the object type. It’s like having a universal remote that works with any TV brand.

What is Polymorphism?

Polymorphism means “many forms.” In OOP, it allows objects of different classes to be treated as objects of a common parent class. The same method call can produce different results based on the actual object type.

Method Overriding (Runtime Polymorphism)

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

class Dog(Animal):
    def make_sound(self):  # Override parent method
        return "Woof!"

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

class Bird(Animal):
    def make_sound(self):
        return "Tweet!"

# Polymorphism in action
animals = [Dog(), Cat(), Bird(), Animal()]

for animal in animals:
    print(f"{animal.__class__.__name__}: {animal.make_sound()}")

# Output:
# Dog: Woof!
# Cat: Meow!
# Bird: Tweet!
# Animal: Some generic sound

Duck Typing

Python’s Dynamic Polymorphism

In Python, polymorphism often works through duck typing - “If it walks like a duck and quacks like a duck, then it must be a duck.”

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

    def walk(self):
        return "Waddles"

class Person:
    def quack(self):
        return "Person imitating duck: Quack!"

    def walk(self):
        return "Walks on two legs"

def make_it_quack(thing):
    """Works with any object that has a quack() method."""
    return thing.quack()

def make_it_move(thing):
    """Works with any object that has a walk() method."""
    return thing.walk()

# Both work the same way
duck = Duck()
person = Person()

print(make_it_quack(duck))    # "Quack!"
print(make_it_quack(person))  # "Person imitating duck: Quack!"
print(make_it_move(duck))     # "Waddles"
print(make_it_move(person))   # "Walks on two legs"

Operator Overloading

Special Methods for Operators

class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __add__(self, other):  # Overload +
        if isinstance(other, Vector):
            return Vector(self.x + other.x, self.y + other.y)
        return NotImplemented

    def __sub__(self, other):  # Overload -
        if isinstance(other, Vector):
            return Vector(self.x - other.x, self.y - other.y)
        return NotImplemented

    def __mul__(self, scalar):  # Overload *
        if isinstance(scalar, (int, float)):
            return Vector(self.x * scalar, self.y * scalar)
        return NotImplemented

    def __eq__(self, other):  # Overload ==
        if isinstance(other, Vector):
            return self.x == other.x and self.y == other.y
        return False

    def __str__(self):  # Overload str()
        return f"Vector({self.x}, {self.y})"

    def __repr__(self):  # Overload repr()
        return f"Vector({self.x}, {self.y})"

# Test operator overloading
v1 = Vector(2, 3)
v2 = Vector(1, 4)

print(f"v1: {v1}")
print(f"v2: {v2}")
print(f"v1 + v2: {v1 + v2}")
print(f"v1 - v2: {v1 - v2}")
print(f"v1 * 3: {v1 * 3}")
print(f"v1 == v2: {v1 == v2}")
print(f"v1 == Vector(2, 3): {v1 == Vector(2, 3)}")

Abstract Base Classes and Polymorphism

Using abc for Interface Contracts

from abc import ABC, abstractmethod

class PaymentMethod(ABC):
    @abstractmethod
    def process_payment(self, amount):
        pass

    @abstractmethod
    def get_payment_info(self):
        pass

class CreditCard(PaymentMethod):
    def __init__(self, card_number, expiry_date):
        self.card_number = card_number
        self.expiry_date = expiry_date

    def process_payment(self, amount):
        return f"Processed ${amount} via credit card ****{self.card_number[-4:]}"

    def get_payment_info(self):
        return f"Credit Card ending in {self.card_number[-4:]}"

class PayPal(PaymentMethod):
    def __init__(self, email):
        self.email = email

    def process_payment(self, amount):
        return f"Processed ${amount} via PayPal account {self.email}"

    def get_payment_info(self):
        return f"PayPal account {self.email}"

class BankTransfer(PaymentMethod):
    def __init__(self, account_number, routing_number):
        self.account_number = account_number
        self.routing_number = routing_number

    def process_payment(self, amount):
        return f"Processed ${amount} via bank transfer to account {self.account_number}"

    def get_payment_info(self):
        return f"Bank account {self.account_number}"

# Polymorphic payment processing
def checkout(payment_method, amount):
    """Works with any PaymentMethod subclass."""
    print(f"Payment info: {payment_method.get_payment_info()}")
    result = payment_method.process_payment(amount)
    print(f"Result: {result}")
    return result

# Test polymorphism
payments = [
    CreditCard("4111111111111111", "12/25"),
    PayPal("user@example.com"),
    BankTransfer("123456789", "021000021")
]

for payment in payments:
    checkout(payment, 99.99)
    print()

Container Protocol

Making Objects Work Like Containers

class ShoppingCart:
    def __init__(self):
        self._items = []

    def add_item(self, item, quantity=1):
        self._items.append((item, quantity))

    def __len__(self):  # len(cart)
        return len(self._items)

    def __getitem__(self, index):  # cart[index]
        return self._items[index]

    def __setitem__(self, index, value):  # cart[index] = value
        self._items[index] = value

    def __delitem__(self, index):  # del cart[index]
        del self._items[index]

    def __iter__(self):  # for item in cart:
        return iter(self._items)

    def __contains__(self, item):  # item in cart
        return any(cart_item[0] == item for cart_item in self._items)

    def __str__(self):
        return f"ShoppingCart with {len(self)} items"

# Test container protocol
cart = ShoppingCart()
cart.add_item("Apple", 3)
cart.add_item("Banana", 2)
cart.add_item("Orange", 1)

print(f"Cart: {cart}")
print(f"Length: {len(cart)}")
print(f"First item: {cart[0]}")
print(f"Contains 'Apple': {'Apple' in cart}")
print(f"Contains 'Grape': {'Grape' in cart}")

print("All items:")
for item, quantity in cart:
    print(f"  {item}: {quantity}")

Comparison Operators

Implementing Rich Comparisons

class Student:
    def __init__(self, name, grade):
        self.name = name
        self.grade = grade

    def __eq__(self, other):  # ==
        if isinstance(other, Student):
            return self.grade == other.grade
        return NotImplemented

    def __lt__(self, other):  # <
        if isinstance(other, Student):
            return self.grade < other.grade
        return NotImplemented

    def __le__(self, other):  # <=
        return self < other or self == other

    def __gt__(self, other):  # >
        return not (self <= other)

    def __ge__(self, other):  # >=
        return not (self < other)

    def __str__(self):
        return f"{self.name} (Grade: {self.grade})"

# Test comparisons
students = [
    Student("Alice", 85),
    Student("Bob", 92),
    Student("Charlie", 78),
    Student("Diana", 85)
]

print("Students sorted by grade:")
for student in sorted(students):
    print(f"  {student}")

alice = students[0]
diana = students[3]

print(f"\n{alice.name} == {diana.name}: {alice == diana}")
print(f"{alice.name} < {diana.name}: {alice < diana}")

Context Managers

__enter__ and __exit__ Methods

class DatabaseConnection:
    def __init__(self, db_name):
        self.db_name = db_name
        self.connected = False

    def __enter__(self):
        print(f"Connecting to {self.db_name}...")
        self.connected = True
        return self  # This becomes the 'as' variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(f"Disconnecting from {self.db_name}...")
        self.connected = False
        # Return False to propagate exceptions, True to suppress

    def query(self, sql):
        if not self.connected:
            raise RuntimeError("Not connected to database")
        return f"Executed: {sql}"

# Test context manager
with DatabaseConnection("mydb") as db:
    result = db.query("SELECT * FROM users")
    print(result)

print(f"Connected: {db.connected}")  # False - automatically disconnected

Function Polymorphism

Functions That Work with Multiple Types

def calculate_area(shape):
    """Works with any shape that has an area() method."""
    return shape.area()

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

class Circle:
    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return 3.14159 * self.radius ** 2

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return 0.5 * self.base * self.height

# Test polymorphic function
shapes = [
    Rectangle(5, 3),
    Circle(4),
    Triangle(6, 4)
]

for shape in shapes:
    print(f"{shape.__class__.__name__} area: {calculate_area(shape)}")

Practical Examples

Example 1: Game Character System

from abc import ABC, abstractmethod

class Character(ABC):
    def __init__(self, name, health):
        self.name = name
        self.health = health

    @abstractmethod
    def attack(self):
        pass

    @abstractmethod
    def defend(self):
        pass

    def take_damage(self, damage):
        self.health -= damage
        if self.health <= 0:
            print(f"{self.name} has been defeated!")
        else:
            print(f"{self.name} takes {damage} damage, {self.health} health remaining")

class Warrior(Character):
    def attack(self):
        return f"{self.name} swings sword for 15 damage!"

    def defend(self):
        return f"{self.name} raises shield, reducing damage by 5"

class Mage(Character):
    def attack(self):
        return f"{self.name} casts fireball for 20 damage!"

    def defend(self):
        return f"{self.name} creates magic barrier, reducing damage by 8"

class Archer(Character):
    def attack(self):
        return f"{self.name} shoots arrow for 12 damage!"

    def defend(self):
        return f"{self.name} dodges, reducing damage by 3"

def battle(attacker, defender):
    """Polymorphic battle function - works with any Character subclass."""
    print(attacker.attack())
    print(defender.defend())

    # Simulate damage calculation
    base_damage = {"Warrior": 15, "Mage": 20, "Archer": 12}[attacker.__class__.__name__]
    defense = {"Warrior": 5, "Mage": 8, "Archer": 3}[defender.__class__.__name__]

    actual_damage = max(0, base_damage - defense)
    defender.take_damage(actual_damage)
    print()

# Test polymorphism
warrior = Warrior("Conan", 100)
mage = Mage("Merlin", 80)
archer = Archer("Robin", 90)

battle(warrior, mage)
battle(mage, archer)
battle(archer, warrior)

Example 2: File Processor System

from abc import ABC, abstractmethod
import os

class FileProcessor(ABC):
    def __init__(self, file_path):
        self.file_path = file_path

    @abstractmethod
    def process(self):
        pass

    @abstractmethod
    def get_info(self):
        pass

    def exists(self):
        return os.path.exists(self.file_path)

class TextFileProcessor(FileProcessor):
    def process(self):
        with open(self.file_path, 'r') as f:
            content = f.read()
        return content.upper()

    def get_info(self):
        lines = 0
        words = 0
        chars = 0

        with open(self.file_path, 'r') as f:
            for line in f:
                lines += 1
                words += len(line.split())
                chars += len(line)

        return f"Text file: {lines} lines, {words} words, {chars} characters"

class ImageFileProcessor(FileProcessor):
    def process(self):
        # Simulate image processing
        return f"Processed image: {self.file_path} (resized and filtered)"

    def get_info(self):
        # Simulate getting image info
        return f"Image file: {self.file_path} (1920x1080, JPEG)"

class CSVFileProcessor(FileProcessor):
    def process(self):
        import csv
        data = []
        with open(self.file_path, 'r') as f:
            reader = csv.reader(f)
            for row in reader:
                data.append(row)
        return f"Processed CSV with {len(data)} rows"

    def get_info(self):
        rows = 0
        columns = 0
        with open(self.file_path, 'r') as f:
            reader = csv.reader(f)
            for i, row in enumerate(reader):
                rows = i + 1
                columns = len(row)
        return f"CSV file: {rows} rows, {columns} columns"

def process_files(processors):
    """Polymorphic function that works with any FileProcessor."""
    results = []
    for processor in processors:
        if processor.exists():
            info = processor.get_info()
            result = processor.process()
            results.append(f"{info}\n{result}")
        else:
            results.append(f"File not found: {processor.file_path}")
    return results

# Test polymorphism
processors = [
    TextFileProcessor("document.txt"),
    ImageFileProcessor("photo.jpg"),
    CSVFileProcessor("data.csv")
]

# Create sample files for testing
with open("document.txt", "w") as f:
    f.write("Hello World\nThis is a test file.")

with open("data.csv", "w") as f:
    f.write("Name,Age,City\nAlice,25,NYC\nBob,30,LA")

results = process_files(processors)
for result in results:
    print(result)
    print("-" * 40)

Example 3: Notification System

from abc import ABC, abstractmethod

class NotificationService(ABC):
    @abstractmethod
    def send(self, message, recipient):
        pass

class EmailService(NotificationService):
    def send(self, message, recipient):
        return f"Email sent to {recipient}: {message}"

class SMSService(NotificationService):
    def send(self, message, recipient):
        # Truncate message for SMS
        truncated = message[:160] + "..." if len(message) > 160 else message
        return f"SMS sent to {recipient}: {truncated}"

class PushNotificationService(NotificationService):
    def send(self, message, recipient):
        return f"Push notification sent to {recipient}: {message}"

class NotificationManager:
    def __init__(self):
        self.services = {}

    def register_service(self, service_type, service):
        self.services[service_type] = service

    def notify(self, service_type, message, recipient):
        """Polymorphic notification method."""
        if service_type in self.services:
            service = self.services[service_type]
            return service.send(message, recipient)
        else:
            return f"Unknown service type: {service_type}"

# Test polymorphism
manager = NotificationManager()
manager.register_service("email", EmailService())
manager.register_service("sms", SMSService())
manager.register_service("push", PushNotificationService())

message = "Your order has been shipped and will arrive in 2-3 business days. " * 5  # Long message

notifications = [
    ("email", "user@example.com"),
    ("sms", "555-0123"),
    ("push", "device_token_123")
]

for service_type, recipient in notifications:
    result = manager.notify(service_type, message, recipient)
    print(result)
    print()

Practice Exercises

Exercise 1: Shape Drawing System

Create a polymorphic drawing system:

  • Base Shape class with draw() method
  • Circle, Rectangle, Triangle subclasses
  • Different drawing implementations
  • Unified drawing function

Exercise 2: Animal Sound Simulator

Build an animal sound system:

  • Base Animal class
  • Different animal subclasses with unique sounds
  • make_sound() method polymorphism
  • Zoo simulation with mixed animals

Exercise 3: Payment Processing System

Create a payment processing system:

  • Abstract PaymentMethod class
  • CreditCard, PayPal, Bitcoin implementations
  • Polymorphic process_payment() method
  • Shopping cart integration

Exercise 4: File Compression System

Build a file compression system:

  • Base Compressor class
  • ZipCompressor, GzipCompressor, TarCompressor
  • Polymorphic compression/decompression
  • File size reporting

Exercise 5: Game Power-up System

Create a game power-up system:

  • Base PowerUp class
  • SpeedBoost, HealthPack, WeaponUpgrade subclasses
  • Polymorphic activate() method
  • Player interaction system

Summary

Polymorphism enables flexible, interchangeable object interfaces:

Method Overriding:

class Parent:
    def method(self):
        return "Parent implementation"

class Child(Parent):
    def method(self):  # Override
        return "Child implementation"

Duck Typing:

def func(obj):
    return obj.quack()  # Works with any object that has quack()

Operator Overloading:

class MyClass:
    def __add__(self, other):  # Overload +
        return MyClass(self.value + other.value)

Abstract Base Classes:

from abc import ABC, abstractmethod

class AbstractClass(ABC):
    @abstractmethod
    def required_method(self):
        pass

Key Benefits:

  • Code reusability
  • Extensibility
  • Interface consistency
  • Runtime flexibility

Common Polymorphic Patterns:

  • Strategy pattern (different algorithms)
  • Factory pattern (object creation)
  • Observer pattern (event handling)
  • Command pattern (action encapsulation)

Next: Special Methods - making your classes magical!