Python November 13 ,2025

Polymorphism — Duck Typing and Interfaces

Table of Contents

  • Introduction to Polymorphism
  • Basic Polymorphism Example
  • Duck Typing in Python
  • Advantages of Duck Typing
  • Interfaces in Python (Implicit Interfaces)
  • When to Use Stricter Interfaces
  • Abstract Base Classes (ABCs)
  • Purpose of ABCs
  • Creating an ABC with abc Module
  • Multiple Abstract Methods Example
  • Partial Implementations in ABCs
  • Operator Overloading and Magic Methods
  • Iterators and Generators in Custom Classes
  • Type Hints and Structural Typing (Protocols)
  • Common OOP Pitfalls
  • OOP Best Practices
  • Complete Example — Library System Design
  • When (and When Not) to Use OOP

Polymorphism (Greek: poly = many, morph = form) means “many forms”.
In programming, it allows the same operation or function name to behave differently depending on the object.

It enables writing flexible and reusable code because different objects can respond to the same function or method in their own unique way.

Example of Polymorphism

class Dog:
    def speak(self):
        return "Bark"

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

# Same function name behaves differently
animals = [Dog(), Cat()]

for animal in animals:
    print(animal.speak())

Output:

Bark
Meow

Explanation:

  • Both Dog and Cat have the same method name speak().
  • But each implements it differently.
  • The loop can call speak() on any object, without caring about its type — that’s polymorphism.

 Duck Typing

Python follows a dynamic typing philosophy, often summarized by:

“If it looks like a duck and quacks like a duck, it’s a duck.”

This means:

  • Python doesn’t require objects to belong to a specific class or implement an interface explicitly.
  • If an object has the required behavior (methods/attributes), it can be used — regardless of its type.

Example of Duck Typing

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

class Person:
    def quack(self):
        print("I’m imitating a duck!")

def make_it_quack(thing):
    thing.quack()   # Only expects the object to have 'quack' method

make_it_quack(Duck())
make_it_quack(Person())

Output:

Quack! Quack!
I’m imitating a duck!

Explanation:

  • make_it_quack() doesn’t check whether the object is a Duck or a Person.
  • It simply calls thing.quack().
  • Both objects “quack”, so it works perfectly — that’s duck typing.

 Advantages of Duck Typing

Flexibility – You can use any object that implements the right behavior.
Less boilerplate – No need for explicit inheritance or interface implementation.
Dynamic nature – Encourages simpler, more readable code.

 Interfaces in Python

Unlike Java or C++, Python does not have explicit interfaces as part of the language.
Instead, “interfaces” are defined implicitly — if an object supports the required methods, it fits the interface.

Example:

  • A “file-like object” in Python is anything that has .read() and .write() methods — not necessarily an actual file.

Stricter (Formal) Interfaces in Python — What Are They?

Stricter (Formal) Interfaces in Python are explicitly defined contracts that specify which methods and properties a class must implement, enforced by the language rather than by convention.

In Python, these are implemented using Abstract Base Classes (ABCs). An ABC defines abstract methods that must be implemented by any subclass, otherwise the subclass cannot be instantiated. This enforcement happens at class creation/instantiation time, not at runtime usage.

This is where Abstract Base Classes (ABCs) come in.

When You Need Stricter / Formal Interfaces

Sometimes, duck typing is too loose:

  • You want to guarantee certain methods exist
  • You want errors to appear at class creation time, not runtime
  • You want clearer API contracts

In these cases, Python provides Abstract Base Classes (ABCs).

10. Abstract Base Classes (ABCs)

An Abstract Base Class (ABC) is a special class that acts as a blueprint for other classes.
It defines a common interface that all its subclasses must follow.

You cannot create objects (instances) of an abstract class directly —
it only defines what methods must exist, not how they are implemented.

Purpose of ABCs

✅ Enforce method implementation in subclasses.
✅ Define contracts for a group of related classes.
✅ Make code more structured and error-free.

Using abc Module

Python provides the abc (Abstract Base Classes) module for this purpose.

You use:

  • ABC as the base class for abstract classes
  • @abstractmethod decorator to mark required methods

Example:

from abc import ABC, abstractmethod

class Shape(ABC):  # Abstract Base Class
    @abstractmethod
    def area(self):
        pass

class Circle(Shape):  # Concrete subclass
    def __init__(self, r):
        self.r = r
    def area(self):
        return 3.14 * self.r * self.r

# s = Shape()  # ❌ Error: Can't instantiate abstract class
c = Circle(5)
print(c.area())

Output:

78.5

Explanation:

  • Shape defines an abstract method area().
  • Any class inheriting from Shape must implement area().
  • Attempting to instantiate Shape directly raises an error.

Benefits of Abstract Base Classes

✅ Provides structure – ensures subclasses follow a defined pattern.
✅ Acts as documentation for expected methods.
✅ Helps with type checking and consistency in large projects.

 Example: Multiple Abstract Methods

from abc import ABC, abstractmethod

class Payment(ABC):
    @abstractmethod
    def pay(self, amount): pass

    @abstractmethod
    def refund(self, amount): pass

class CreditCardPayment(Payment):
    def pay(self, amount):
        print(f"Paid {amount} using Credit Card")

    def refund(self, amount):
        print(f"Refunded {amount} to Credit Card")

c = CreditCardPayment()
c.pay(1000)
c.refund(500)

Output:

Paid 1000 using Credit Card
Refunded 500 to Credit Card

Explanation:

  • Payment defines two required methods: pay() and refund().
  • Any subclass of Payment must implement both.

Partial Implementation

A subclass can implement some abstract methods and remain abstract itself.
It becomes instantiable only when all abstract methods are defined.

11. Operator overloading and magic methods

Python special (dunder) methods let objects behave like builtins:

  • __str__/__repr__ — string representations
  • __add__/__sub__/__mul__ — arithmetic operators
  • __lt__/__eq__/__hash__ — comparison and hashing
  • __len__/__iter__/__next__ — container and iterator behavior
  • __call__ — make instances callable

Example:

class Vector:
    def __init__(self, x,y): self.x,self.y = x,y
    def __add__(self, other): return Vector(self.x+other.x, self.y+other.y)
    def __repr__(self): return f"Vector({self.x},{self.y})"

Now v1 + v2 uses __add__. Implementing __eq__ and __hash__ carefully matters for using objects as dict keys or in sets.

12. Iterators and generators in classes

To make an object iterable, implement __iter__() returning an iterator (object with __next__()), or make __iter__ a generator.

class Counter:
    def __init__(self, n): self.n = n
    def __iter__(self):
        for i in range(self.n):
            yield i

This allows for x in Counter(3):.

13. Composition vs Inheritance (prefer composition often)

  • Inheritance says “is-a”: Car is a Vehicle.
  • Composition says “has-a”: Car has an Engine.

Composition often yields looser coupling and better flexibility; prefer composition for reusing behavior rather than creating deep inheritance hierarchies.

Example composition:

class Engine:
    def start(self): print("engine started")
class Car:
    def __init__(self): self.engine = Engine()
    def drive(self):
        self.engine.start()
        print("car driving")

 

14. Common pitfalls and best practices

Pitfalls

  • Mutable class attributes leading to shared state bugs.
  • Overusing inheritance (deep hierarchies increase complexity).
  • Catching exceptions too broadly hides bugs.
  • Relying on __del__ for resource management.
  • Not implementing __eq__ & __hash__ carefully when objects are used in sets/dicts.

Best practices

  • Keep classes small and focused (Single Responsibility).
  • Prefer composition over inheritance when possible.
  • Use properties for controlled attribute access.
  • Keep __init__ simple (avoid heavy work); use factory methods for complex creation.
  • Document public API of classes and methods.
  • Write unit tests for object behavior, especially edge cases.
  • Use dataclasses for simple data containers.
  • Use abc for clear abstract interfaces when needed.

15. Example: complete small OOP design — Library system

A small sample tying many concepts together (encapsulation, inheritance, abstract base class, composition):

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from datetime import date

@dataclass
class Person:
    name: str

class Item(ABC):
    def __init__(self, title: str):
        self.title = title
        self._borrowed_by = None

    def is_available(self): 
        return self._borrowed_by is None

    def borrow(self, member: 'Member'):
        if not self.is_available():
            raise ValueError("Item not available")
        self._borrowed_by = member
        self._borrow_date = date.today()

    def return_item(self):
        self._borrowed_by = None
        self._borrow_date = None

    @abstractmethod
    def borrow_period(self) -> int: pass

class Book(Item):
    def borrow_period(self) -> int:
        return 21  # days

class DVD(Item):
    def borrow_period(self) -> int:
        return 7

@dataclass
class Member(Person):
    member_id: int
    borrowed: list = field(default_factory=list)

    def borrow_item(self, item: Item):
        item.borrow(self)
        self.borrowed.append(item)

    def return_item(self, item: Item):
        item.return_item()
        self.borrowed.remove(item)

This design shows:

  • Item is abstract (requires borrow_period).
  • Member composes Item usage.
  • Encapsulation: _borrowed_by is internal (single underscore).
  • dataclass for simple data structs.

Patterns and when to use OOP

OOP isn't the only way to organize code — functional or procedural approaches often fit. Use OOP when:

  • You model domain objects with state and behavior.
  • You need to group related behavior and maintain invariants.
  • You want polymorphism (different implementations under same interface).
  • You need to model complex mutable state over time.

Design patterns (Factory, Strategy, Adapter, Observer) are often implemented with classes — but remember patterns are solutions to recurring design problems, not absolute requirements.

Summary

Polymorphism allows the same operation to behave differently depending on the object, enabling flexible, reusable, and extensible code. In Python, polymorphism is most commonly achieved through duck typing, where behavior matters more than an object’s concrete type.

Python’s dynamic nature encourages implicit interfaces — if an object implements the required methods, it can be used without formal inheritance. This makes code simpler and more adaptable, but also places responsibility on developers to follow conventions carefully.

When greater reliability and clarity are needed, Python provides Abstract Base Classes (ABCs) to define stricter, formal interfaces. ABCs act as enforceable contracts that guarantee required method implementation, helping catch errors early and improving maintainability in larger systems.

Beyond interfaces, Python supports rich polymorphic behavior through operator overloading, magic methods, iterators, and generators, allowing custom objects to integrate seamlessly with built-in language features. Thoughtful use of composition over inheritance, combined with clear abstractions, leads to more flexible and maintainable designs.

Finally, effective object-oriented programming in Python is about balance. OOP is most powerful when modeling real-world entities with shared behavior, but it should be applied judiciously. Understanding when to use duck typing, when to enforce interfaces, and when to avoid OOP altogether is key to writing clean, scalable Python code.

Next Blog- Iterators and Generators in Python

Sanjiv
0

You must logged in to post comments.

Get In Touch

Kurki bazar Uttar Pradesh

+91-8808946970

techiefreak87@gmail.com