Python Polymorphism: Duck Typing, Method Overriding and ABCs
Learn Python polymorphism: one interface for many classes, method overriding, duck typing, abstract base classes, Protocol and singledispatch, with examples.
Look at the plug point on the wall of any office in Indore. You can plug in a kettle, a laptop charger, a desk fan or a phone charger. The socket does not know or care what is on the other end. It offers one interface, two pins and 230 volts, and each device does its own thing with it.
The kettle heats water. The charger fills a battery. The fan spins. If the office buys a new air purifier next month, nobody rewires the socket. The purifier just needs the right plug.
That is polymorphism. The socket is your calling code. The plug shape is the interface, such as a method called export(). Each device is a different class with its own implementation of that method.
Polymorphism in Python is the ability of different classes to respond to the same method call or operation in their own way, so one piece of code can work with many types of object without checking which type it has.
This lesson builds on Python inheritance, where you overrode parent methods. Examples target Python 3.12+.
What is polymorphism in Python?
You have relied on polymorphism since your first loop. Built-in functions and operators already behave differently depending on the type they receive.
items = ["Indore", [1450, 980, 2300], {"city": "Ujjain", "orders": 41}, (3, 5)]
for item in items:
print(f"len({item!r}) = {len(item)}")
print(1450 + 980) # numbers add
print("Old " + "Palasia") # strings join
print([1, 2] + [3]) # lists concatenate
print(sorted("rajwada"), sorted([3, 1, 2]))len() does not contain separate code for strings, lists and dicts. It asks the object for its length, and each type answers in its own way. Your classes can join in. How + and len() hook into your own classes is the subject of Python operator overloading.
Polymorphism with method overriding
The classic form uses a parent class with a method and children that override it. The calling code talks only to the shared method name.
import json
class Exporter:
def export(self, rows):
raise NotImplementedError
class CsvExporter(Exporter):
def export(self, rows):
lines = [",".join(rows[0])] + [",".join(str(v) for v in r.values()) for r in rows]
return "\n".join(lines)
class JsonExporter(Exporter):
def export(self, rows):
return json.dumps(rows)
class MarkdownExporter(Exporter):
def export(self, rows):
line = lambda cells: "| " + " | ".join(map(str, cells)) + " |"
rule = "|" + "---|" * len(rows[0])
return "\n".join([line(rows[0]), rule] + [line(r.values()) for r in rows])
rows = [{"city": "Indore", "orders": 412}, {"city": "Bhopal", "orders": 298}]
for exporter in [CsvExporter(), JsonExporter(), MarkdownExporter()]:
print(f"--- {type(exporter).__name__}")
print(exporter.export(rows))The loop never asks "is this a CSV exporter?". It calls export() and trusts each object. Adding a fourth format later means writing one new class; the loop does not change. The JSON format itself is covered in Python JSON.
The calling code stays the same; the object decides which implementation runs.
What is duck typing in Python?
Python does not actually require a shared parent. If an object has the method you call, it works. The phrase comes from "if it walks like a duck and quacks like a duck, it is a duck". Python checks what an object can do, not what it is.
import io
class FakeUpiFeed:
"""Not a file, but it has a read() method."""
def read(self):
return "450\n1200\n799\n"
def total_amount(source):
return sum(float(line) for line in source.read().split())
print(total_amount(io.StringIO("1450\n980\n2300\n"))) # an in-memory file
print(total_amount(FakeUpiFeed())) # a custom classtotal_amount works with a real file object, an io.StringIO buffer or a test double, because all three have read(). That is why duck typing is so useful for testing data code: you can feed a function fake inputs without touching disk. Real file objects were covered in Python file handling.
If an object lacks the method, you get an AttributeError at the moment of the call. In keeping with Python's "ask forgiveness" style, you usually let that error surface rather than checking hasattr() in advance.
Quick check
What does duck typing mean in Python?
Python checks behaviour at the moment of the call. If the method exists, the object is accepted.
Abstract base classes: enforcing the interface
Duck typing is flexible, but it fails late. If someone writes an exporter and misspells export as exprot, nothing complains until the export runs. An abstract base class (ABC) catches this earlier, when the object is created.
from abc import ABC, abstractmethod
class Exporter(ABC):
@abstractmethod
def export(self, rows):
"""Return the rows as text."""
def save_note(self, rows): # concrete, shared method
return f"{type(self).__name__} exported {len(rows)} rows"
class CsvExporter(Exporter):
def export(self, rows):
return "\n".join(",".join(map(str, r.values())) for r in rows)
class BrokenExporter(Exporter):
def exprot(self, rows): # typo: export is missing
return ""
print(CsvExporter().save_note([{"a": 1}, {"a": 2}]))
for cls in (Exporter, BrokenExporter):
try:
cls()
except TypeError:
print(f"{cls.__name__}: cannot be created, export() not implemented")An ABC can mix abstract methods, which subclasses must provide, with normal methods they inherit for free. You cannot create an instance of the ABC itself or of any subclass that still has an abstract method. The full rules are in the abc module documentation.
An ABC turns a forgotten method into an immediate TypeError instead of a late surprise.
Protocols: duck typing for type checkers
typing.Protocol describes an interface without inheritance. Any class with matching methods counts, and tools like mypy check it before the code runs. It suits codebases that already use Python type hints.
from typing import Protocol
class Readable(Protocol):
def read(self) -> str: ...
class SmsFeed: # does not inherit from Readable
def read(self) -> str:
return "1200 799"
def count_values(src: Readable) -> int:
return len(src.read().split())
print(count_values(SmsFeed()))Quick check
A subclass of an ABC does not implement one @abstractmethod. When does the error appear?
Instantiating a class that still has abstract methods raises TypeError.
Ways to get polymorphism in Python
| Approach | Needs a common parent? | Checked when? | Best for |
|---|---|---|---|
| Method overriding | Yes | At call time | Classes that share real code |
| Duck typing | No | At call time | Small scripts, test doubles |
abc.ABC |
Yes | When the object is created | Plugin families that must follow a contract |
typing.Protocol |
No | By a type checker, before running | Typed codebases, third-party classes |
functools.singledispatch |
No | At call time, by argument type | One function, different behaviour per type |
Does Python support function overloading?
Not in the Java or C++ sense. If you define two functions with the same name, the second simply replaces the first. Use default arguments for optional parameters, or functools.singledispatch when behaviour depends on the type of the first argument.
from datetime import date
from functools import singledispatch
@singledispatch
def fmt(value):
return str(value)
@fmt.register
def _(value: float):
return f"{value:,.2f}"
@fmt.register
def _(value: date):
return value.strftime("%d %b %Y")
for v in [48200.5, date(2026, 11, 12), "Indore", 7]:
print(type(v).__name__, "->", fmt(v))singledispatch picks the registered function that matches the type of the first argument, falling back to the original for anything unregistered.
Common mistakes with polymorphism
- Type checks instead of methods. A chain of
if isinstance(x, CsvExporter) ... elif ...defeats the purpose. Put the behaviour in each class. - Inconsistent signatures. If one
export(rows)takes a list and another needs(rows, path), callers break. Keep overrides compatible. - Returning different kinds of result. One exporter returning text and another writing a file is confusing. Agree on one contract.
- Using ABCs everywhere. For a two-class script, duck typing is simpler.
- Defining a function twice expecting overloading. The later definition wins silently.
Key takeaways
- Polymorphism lets one call work with many types, each in its own way.
- Built-ins like
len()and+are already polymorphic. - Overriding gives polymorphism through inheritance; duck typing gives it without.
- ABCs enforce that subclasses implement required methods.
typing.Protocolchecks interfaces statically without inheritance.- Python has no signature overloading; use defaults or
singledispatch.
Next, make +, == and len() work on your own classes in Python operator overloading. Designing exporters and report plugins like these is the kind of exercise you build in Debugshala's data analytics classes for career switchers.
FAQ
What is polymorphism in Python with an example?
Polymorphism is when the same operation works on different types, each in its own way. len("Indore") counts characters while len([1, 2, 3]) counts items. With your own classes, a CsvExporter and a JsonExporter can both have an export() method, and a loop can call it without knowing which one it holds.
What is the difference between overloading and overriding in Python?
Overriding means a subclass redefines a method it inherited, and Python fully supports it. Overloading means several functions with the same name but different parameters. Python does not support that directly; a second definition replaces the first. You get similar results with default arguments, *args, or functools.singledispatch.
Is duck typing the same as polymorphism?
Duck typing is one way Python achieves polymorphism. Classical polymorphism relies on a shared parent class and overridden methods. Duck typing drops the parent requirement: any object with the right methods works. Most Python code uses duck typing, with ABCs or Protocols added when an explicit contract helps.
When should I use an abstract base class in Python?
Use an ABC when several classes must follow the same contract and a missing method would cause a hard-to-find bug, such as a family of exporters or data source plugins. The ABC raises TypeError the moment someone creates an incomplete subclass object, which is far earlier than a failing call.
How do I prepare OOP questions for analyst interviews?
Expect to explain polymorphism, overriding versus overloading and duck typing, often with a short code sample. Practise writing two or three classes with a shared method, then try OOP and polymorphism questions on Debugshala Test. Debugshala in Old Palasia, Indore, also offers a free demo class for its analytics programme.
From the team behind this site
Go further with the Data Analytics Course at Debugshala
These tutorials are written by Debugshala, a training and placement institute at Old Palasia, Indore. The classroom course adds live mentoring, real projects and interview preparation.
- Offline classes at Old Palasia, Indore
- Free demo class before you pay
- Non-IT, fresher and year-gap friendly
- Placement assistance
700+ students placed · 200+ hiring partners · 4.9★ Google rating