Web Analytics

Polymorphism

Intermediate ~25 min read

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).

Output
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.

Output
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.

Output
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.

Output
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:

  • AudioFile and VideoFile classes inheriting from MediaFile
  • ImageFile class (no inheritance - demonstrates duck typing)
  • All classes implement play() and get_info() methods
  • play_all() function that works with any media type
  • total_duration() using duck typing (some media has duration, some doesn't)
Output
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.