Python Tutorial
Polymorphism and Method Overriding
Polymorphism means "many forms": the same operation works on different types, each doing the right thing for itself. len() works on strings, lists and dicts; shape.area() computes a circle's or a rectangle's area. Code written against the shared interface works with every type that provides it, including types created later.
This lesson covers polymorphism through inheritance and overriding, duck typing (Python's most common form), operator polymorphism, and typing.Protocol for type-checked duck typing.
Polymorphism Through Overriding
Subclasses override a method defined by a base class; code that calls the method on the base type automatically uses each subclass's version at runtime. A payment system can loop over CardPayment, UpiPayment and WalletPayment objects calling pay() without checking types.
Duck Typing
"If it walks like a duck and quacks like a duck, it's a duck." Python does not require a common base class: any object with the needed method works. File-like objects, iterables and context managers are all duck-typed protocols. Write functions that rely on behaviour, not on type() checks.
Built-in and Operator Polymorphism
+ adds numbers, concatenates strings and lists, and can be defined for your own classes via __add__. Built-ins like len(), str(), iter() call special methods (__len__, __str__, __iter__), so your classes can join in.
Protocols
typing.Protocol describes an interface structurally: any class with matching methods satisfies it, without inheriting from it. Static type checkers such as mypy then verify duck-typed code; with @runtime_checkable you can also use isinstance.
Examples
Polymorphism through a common base class
import math
class Shape:
def area(self):
raise NotImplementedError
class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return math.pi * self.r ** 2
class Rectangle(Shape):
def __init__(self, w, h):
self.w, self.h = w, h
def area(self):
return self.w * self.h
class Triangle(Shape):
def __init__(self, b, h):
self.b, self.h = b, h
def area(self):
return 0.5 * self.b * self.h
shapes = [Circle(1), Rectangle(3, 4), Triangle(6, 2)]
for s in shapes:
print(f"{type(s).__name__:<10} {s.area():.2f}")
print("total:", round(sum(s.area() for s in shapes), 2))
Circle 3.14
Rectangle 12.00
Triangle 6.00
total: 21.14
Duck typing: no shared base class needed
class UpiPayment:
def pay(self, amount):
return f"Paid Rs.{amount} via UPI"
class CardPayment:
def pay(self, amount):
return f"Charged Rs.{amount} to card"
class GiftVoucher:
def pay(self, amount):
return f"Redeemed voucher for Rs.{amount}"
def checkout(method, amount):
return method.pay(amount) # works for anything with pay()
for m in [UpiPayment(), CardPayment(), GiftVoucher()]:
print(checkout(m, 499))
Paid Rs.499 via UPI
Charged Rs.499 to card
Redeemed voucher for Rs.499
Operator and built-in polymorphism
print(2 + 3, "py" + "thon", [1] + [2], (1,) + (2,))
print(len("hello"), len([1, 2]), len({"a": 1}))
class Playlist:
def __init__(self, songs):
self.songs = songs
def __len__(self):
return len(self.songs)
def __add__(self, other):
return Playlist(self.songs + other.songs)
mix = Playlist(["a", "b"]) + Playlist(["c"])
print(len(mix), mix.songs)
5 python [1, 2] (1, 2)
5 2 1
3 ['a', 'b', 'c']
Structural typing with Protocol
from typing import Protocol, runtime_checkable
@runtime_checkable
class Notifier(Protocol):
def send(self, message: str) -> str: ...
class EmailNotifier:
def send(self, message: str) -> str:
return f"email: {message}"
class SmsNotifier:
def send(self, message: str) -> str:
return f"sms: {message}"
class Logger:
def log(self, message: str) -> None: ...
def alert(n: Notifier, message: str) -> str:
return n.send(message)
print(alert(EmailNotifier(), "Order shipped"), "|", alert(SmsNotifier(), "OTP 4821"))
print(isinstance(EmailNotifier(), Notifier), isinstance(Logger(), Notifier))
email: Order shipped | sms: OTP 4821
True False
Common Mistakes
- Writing if type(x) == A ... elif type(x) == B chains instead of calling a polymorphic method.
- Overriding a method with an incompatible signature, breaking callers.
- Forcing inheritance where duck typing or a Protocol is enough.
- Returning different types from overrides of the same method.
Key Points to Remember
- Polymorphism lets one interface work with many types.
- Overriding in subclasses gives type-specific behaviour behind a common method.
- Duck typing: objects are usable if they have the needed methods.
- Operators and built-ins are polymorphic through special methods.
- typing.Protocol type-checks duck-typed interfaces.
Practice the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.