Python Polymorphism · Examples
Object-Oriented Python

Python Polymorphism Examples

Short, runnable programs that show python polymorphism and duck typing in action. Read the explanation, press Run, then change the code and try your own ideas. New to the topic? Start with the Python Polymorphism and Duck Typing lesson.

1for loops and in work on anything iterable

A for loop does not care whether it receives a string, a list, a dictionary or a range. Each of them knows how to hand out its items, and in works across all of them too. That is polymorphism built into the language.

for loops and in work on anything iterable
things = ["cat", [10, 20], {"a": 1}, range(3)]
for thing in things:
    print(type(thing).__name__, list(thing))
print("a" in "cat", 20 in [10, 20], "a" in {"a": 1}, 2 in range(3))
Expected outputstr ['c', 'a', 't']
list [10, 20]
dict ['a']
range [0, 1, 2]
True True True True

2A parent that calls the child's version

Exporter.export() is written once and calls self.format_row(). Each child overrides only format_row, and Python picks the right one because self is the child object. The base class gets a sensible default.

A parent that calls the child's version
class Exporter:
    def export(self, rows):
        return "\n".join(self.format_row(row) for row in rows)

    def format_row(self, row):
        return " ".join(str(cell) for cell in row)

class CsvExporter(Exporter):
    def format_row(self, row):
        return ",".join(str(cell) for cell in row)

class MarkdownExporter(Exporter):
    def format_row(self, row):
        return "| " + " | ".join(str(cell) for cell in row) + " |"

rows = [("Asha", 91), ("Ben", 78)]
for exporter in [Exporter(), CsvExporter(), MarkdownExporter()]:
    print(exporter.export(rows))
Expected outputAsha 91
Ben 78
Asha,91
Ben,78
| Asha | 91 |
| Ben | 78 |

3Replace an isinstance() chain with a method

The chain handles two file types and quietly returns None for the third. Asking each object for its own preview() works for all three, and for any class you add later.

Replace an isinstance() chain with a method
class TextFile:
    def preview(self):
        return "first 3 lines of text"

class ImageFile:
    def preview(self):
        return "small thumbnail"

class PdfFile:
    def preview(self):
        return "page 1 as an image"

def preview_with_chain(item):
    if isinstance(item, TextFile):
        return "text: " + item.preview()
    elif isinstance(item, ImageFile):
        return "image: " + item.preview()

files = [TextFile(), ImageFile(), PdfFile()]
print([preview_with_chain(f) for f in files])
print([f.preview() for f in files])
Expected output['text: first 3 lines of text', 'image: small thumbnail', None]
['first 3 lines of text', 'small thumbnail', 'page 1 as an image']

4A stand-in object for testing

Duck typing makes it easy to swap a real dependency for a test double. welcome() only needs something with a send() method, so a FakeMailer that records messages works without sending anything.

A stand-in object for testing
class FakeMailer:
    def __init__(self):
        self.outbox = []

    def send(self, to, text):
        self.outbox.append((to, text))

class ConsoleMailer:
    def send(self, to, text):
        print(f"[console] {to}: {text}")

def welcome(name, address, mailer):
    mailer.send(address, f"Welcome, {name}!")

welcome("Asha", "asha@example.com", ConsoleMailer())
fake = FakeMailer()
welcome("Ben", "ben@example.com", fake)
welcome("Chen", "chen@example.com", fake)
print(fake.outbox)
Expected output[console] asha@example.com: Welcome, Asha!
[('ben@example.com', 'Welcome, Ben!'), ('chen@example.com', 'Welcome, Chen!')]

5Optional methods with getattr()

Not every resource needs clean-up. getattr(obj, name, default) looks the method up safely, and callable() makes sure what you found can be called. Objects without close() are simply skipped.

Optional methods with getattr()
class Database:
    def close(self):
        print("database closed")

class Logger:
    pass                      # nothing to close

class Cache:
    def close(self):
        print("cache cleared")

def shutdown(resources):
    for resource in resources:
        close = getattr(resource, "close", None)
        if callable(close):
            close()
        else:
            print(type(resource).__name__, "needs no cleanup")

shutdown([Database(), Logger(), Cache()])
Expected outputdatabase closed
Logger needs no cleanup
cache cleared

Practise next

Python Polymorphism and Duck Typing exercises → Back to the lesson

Python Dunder (Magic) Methods examplesPython Inheritance examplesPython Abstract Classes (ABC) examples