Polymorphism
Polymorphism ("many forms") allows different objects to respond to the same method call in their own way. Python embraces polymorphism through duck typing - focusing on what an object can do (its methods) rather than what it is (its type).
Python's Approach to Polymorphism
Unlike Java or C++, Python doesn't require explicit interfaces or inheritance for polymorphism. If an object has the right methods, it can be used - this is called duck typing: "If it walks like a duck and quacks like a duck, it's a duck."
Polymorphism Basics
Polymorphism allows you to write code that works with objects of different types, as long as they share a common interface (same method names).
Click Run to execute your code
Write Generic Functions
The key benefit of polymorphism is writing functions that work with ANY object having the right methods. Your function doesn't need to know about every possible type - it just uses the interface.
Polymorphism Through Inheritance
Inheritance naturally creates polymorphism. A base class defines the interface, and child classes provide different implementations through method overriding.
Click Run to execute your code
Open/Closed Principle
Polymorphism enables the Open/Closed Principle: code is open for extension (add new shapes without changing existing code) but closed for modification (existing functions work with new types automatically).
Duck Typing
Python's duck typing means you don't need inheritance for polymorphism. Any object with the right methods will work - the type doesn't matter, only the behavior.
Click Run to execute your code
EAFP: Easier to Ask Forgiveness than Permission
Python's preferred style is EAFP - try to use the object and handle exceptions if it fails.
Don't check types with isinstance() before using an object. Let duck typing work!
Python Protocols
Protocols are informal interfaces that enable polymorphism with built-in functions like
len(), for loops, and operators. Implement special methods to make
your objects work with Python's syntax.
Click Run to execute your code
Make Objects Pythonic
By implementing protocols (__len__, __iter__, __contains__, etc.),
your custom objects can work with Python's built-in functions and syntax, making them feel like
native Python types.
Common Mistakes
1. Unnecessary type checking
# Bad - defeats duck typing
def process(obj):
if isinstance(obj, MyClass):
return obj.process()
raise TypeError("Must be MyClass!")
# Good - just use it
def process(obj):
return obj.process() # Works with any class that has process()
2. Not handling missing methods gracefully
# Bad - crashes if method missing
def display(obj):
return obj.render() # AttributeError if no render()
# Good - EAFP style
def display(obj):
try:
return obj.render()
except AttributeError:
return str(obj) # Fallback
3. Forgetting to implement all interface methods
# Problem - incomplete implementation
class MyIterator:
def __iter__(self):
return self
# Missing __next__! Will fail at runtime.
# Complete implementation
class MyIterator:
def __init__(self, data):
self.data = data
self.index = 0
def __iter__(self):
return self
def __next__(self):
if self.index >= len(self.data):
raise StopIteration
value = self.data[self.index]
self.index += 1
return value
4. Breaking the interface contract
# Bad - same method name, different behavior
class GoodDuck:
def speak(self):
return "Quack" # Returns string
class BadDuck:
def speak(self):
print("Quack") # Prints instead of returning!
# Now this breaks:
message = BadDuck().speak() # message is None!
5. Over-relying on inheritance for polymorphism
# Overly complex - forced inheritance
class BaseProcessor:
def process(self): pass
class CSVProcessor(BaseProcessor):
def process(self): ...
class JSONProcessor(BaseProcessor):
def process(self): ...
# Simpler - just use duck typing
# No base class needed! Just have process() method
class CSVProcessor:
def process(self): ...
class JSONProcessor:
def process(self): ...
Exercise: Media Player
Task: Create a polymorphic media player system using both inheritance and duck typing.
Requirements:
AudioFileandVideoFileclasses inheriting fromMediaFileImageFileclass (no inheritance - demonstrates duck typing)- All classes implement
play()andget_info()methods play_all()function that works with any media typetotal_duration()using duck typing (some media has duration, some doesn't)
Click Run to execute your code
Show Solution
class MediaFile:
def __init__(self, filename, duration):
self.filename = filename
self.duration = duration
def play(self):
raise NotImplementedError
def get_info(self):
return f"{self.filename} ({self.duration}s)"
class AudioFile(MediaFile):
def __init__(self, filename, duration, bitrate):
super().__init__(filename, duration)
self.bitrate = bitrate
def play(self):
return f"Playing audio: {self.filename}"
def get_info(self):
return f"{self.filename} ({self.duration}s, {self.bitrate}kbps)"
class VideoFile(MediaFile):
def __init__(self, filename, duration, resolution):
super().__init__(filename, duration)
self.resolution = resolution
def play(self):
return f"Playing video: {self.filename}"
def get_info(self):
return f"{self.filename} ({self.duration}s, {self.resolution})"
class ImageFile: # No inheritance - duck typing!
def __init__(self, filename, dimensions):
self.filename = filename
self.dimensions = dimensions
# No duration attribute!
def play(self):
return f"Displaying image: {self.filename}"
def get_info(self):
return f"{self.filename} ({self.dimensions})"
def play_all(media_list):
for media in media_list:
print(media.play())
def total_duration(media_list):
total = 0
for media in media_list:
try:
total += media.duration
except AttributeError:
pass # Skip items without duration
return total
# Test
media = [
AudioFile("song.mp3", 180, 320),
VideoFile("movie.mp4", 7200, "1920x1080"),
ImageFile("photo.jpg", "4000x3000"),
]
for m in media:
print(m.get_info())
play_all(media)
print(f"Total duration: {total_duration(media)}s")
Summary
- Polymorphism: Different objects responding to the same method call
- Duck typing: "If it has the right methods, use it" - no type checks
- EAFP: Try to use the object, handle exceptions if it fails
- Protocols: Implement
__len__,__iter__, etc. for Python integration - Inheritance polymorphism: Base class defines interface, children override
- Write generic functions that work with any object having the right methods
What's Next?
Now that you understand polymorphism and protocols, learn about Special Methods
(dunder methods) - the double-underscore methods like __init__, __str__,
and __eq__ that make your classes work with Python's syntax.
Enjoying these tutorials?