NOETRION

Choose a topic

DAY 22 OF 30 · OBJECTS AND USEFUL TOOLS

Polymorphism and method overriding

Let objects share an interface and distinguish overriding from Python operator overloading.

90–120 minutes suggested study timePython 3.12Not yet completed

Today's outcomes

  • Use common methods without checking every concrete type.
  • Explain why repeated same-name definitions replace earlier ones.
  • Use defaults or an explicit method instead of signature-based overloading.

Before you start: Complete Day 21 and its practical challenge.

Understand the concept

Polymorphism lets one operation work with different objects

A function can call a method shared by several objects without checking every concrete class. If the accepted objects each provide a suitable describe method, the caller can build a report through that interface. This style is often called duck typing: behavior relevant to the task matters more than a particular inheritance label.

The interface still has a contract. Does describe take no additional arguments? Does it return a string? Does it change state? A method having the same spelling is not enough if its behavior violates what the caller expects. Tests should include each intended object kind and a new compatible object that was not anticipated by the original caller.

Overriding replaces inherited behavior for a specialized class

An overridden method has the same name as a method inherited from a base class. Calling it on the specialized object finds the subclass implementation under normal attribute lookup. It may extend the base behavior with super, or implement the same interface differently.

Overriding is different from defining multiple same-name methods inside one class body. In ordinary Python, a later definition replaces the earlier binding. Python does not automatically choose a method based on the number or types of arguments as some languages do for signature-based overloading.

Optional arguments and explicit operations are clearer than imagined overloads

Use a default parameter when one operation genuinely has an optional input. Use different method names when the operations mean different things. Type-checking overload annotations document signatures for type checkers; they do not supply runtime dispatch implementations by themselves.

Python does support operator customization through special methods such as __add__, and there are separate dispatch utilities. Those are specific mechanisms with their own contracts, not evidence that repeated same-name method definitions dispatch automatically.

Term Meaning in this lesson Example
Polymorphism Several object kinds support one caller's interface report calls describe
Overriding Subclass specializes an inherited method TimedTask.describe
Optional parameter One implementation handles a documented option describe(detailed=False)
Operator customization Special method participates in an operator add for addition
Repeated definition Later class-body binding replaces an earlier one Two ordinary defs with one name

Worked example

class TopicNote:
    def __init__(self, topic):
        self.topic = topic

    def describe(self):
        return f"Note: {self.topic}"

class Timer:
    def __init__(self, minutes):
        self.minutes = minutes

    def describe(self):
        return f"Timer: {self.minutes} min"

def render_report(items):
    return "\n".join(item.describe() for item in items)

report = render_report([TopicNote("functions"), Timer(30)])
assert report == "Note: functions\nTimer: 30 min"
assert render_report([]) == ""
print(report)

These two classes do not inherit from one common application base, yet the reporting function accepts both through the documented describe interface. The fixture assumes validated constructor inputs; this lesson isolates interface behavior rather than duplicating every earlier validation example.

A separate example demonstrates the repeated-definition rule explicitly:

class Formatter:
    def format(self, text):
        return text

    def format(self, text, prefix):
        return prefix + text

formatter = Formatter()
assert formatter.format("hello", "note: ") == "note: hello"
try:
    formatter.format("hello")
except TypeError:
    print("Later definition replaced the earlier one")
else:
    raise AssertionError("There is no automatic signature overload here")

This deliberately flawed class is a diagnostic demonstration, not the recommended implementation. One format(self, text, prefix='') would support both documented call forms through one implementation.

Guided practice

  1. Predict: add another compatible object with describe returning a string. Does render_report need a class-name check? Explain why it does not under this contract.
  2. Modify: repair Formatter using a default prefix. Test both the one-argument and two-argument calls without defining format twice.
  3. Reconstruct: write a report function that accepts any object with a zero-argument summary method returning text. Give it two unrelated kinds of objects and document that interface.

Do not catch every AttributeError from a polymorphic caller and substitute blank text. That could conceal an actual bug inside a method rather than an absent interface. If arbitrary external objects are allowed, define validation or adaptation deliberately.

Daily challenge

Create two object kinds, PlainTask and TimedTask, that both provide describe(detailed=False). PlainTask returns its title in either mode. TimedTask returns its title normally and title (minutes min) in detailed mode. Implement task_report(items, detailed=False) without inspecting class names or using an if chain for each type.

Call Expected result
Two tasks, default mode Two titles separated by newline
Same tasks, detailed mode Plain title and timed title with duration
Empty list Empty string
New compatible object added Its describe result included without caller changes

Use one implementation per method name in each class. This challenge tests a common interface and optional behavior, not signature-based overloading.

Reference solution

Compare after attempting the challenge
class PlainTask:
    def __init__(self, title):
        self.title = title

    def describe(self, detailed=False):
        return self.title

class TimedTask:
    def __init__(self, title, minutes):
        self.title, self.minutes = title, minutes

    def describe(self, detailed=False):
        return f"{self.title} ({self.minutes} min)" if detailed else self.title

def task_report(items, detailed=False):
    return "\n".join(item.describe(detailed=detailed) for item in items)

tasks = [PlainTask("Read"), TimedTask("Code", 45)]
assert task_report(tasks) == "Read\nCode"
assert task_report(tasks, True) == "Read\nCode (45 min)"
assert task_report([]) == ""

class ReviewCard:
    def describe(self, detailed=False):
        return "Review card"

assert task_report([ReviewCard()]) == "Review card"
print(task_report(tasks, True))

Debug and explain

If a previously supported call now raises a missing-argument TypeError, inspect whether a second definition replaced the first. If a report needs a new branch for every object type, inspect whether the shared interface is genuinely consistent. If a method returns a number where join expects text, inspect the interface contract rather than converting everything silently.

Explain polymorphism without saying “all classes have the same parent.” Then distinguish method overriding, optional arguments, and operator customization with one example each. Python's actual behavior should guide the terminology rather than an inaccurate diagram label.

Review and next step

Save the report tests and repaired Formatter. Rebuild the interface from memory at your next review. Day 23 uses standard-library date and time objects, applying your object-reading skills to useful types you did not define yourself.

Sources and verification

Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.

DAILY CHECKPOINT

Check your understanding

Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.

01. What does duck typing emphasize?

02. What is method overriding?

03. Does defining the same method twice create signature overloads?

04. How can one function support optional input?

05. What should a polymorphic caller rely on?

Review your practical work

Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.

Pass the knowledge check and finish the practical self-review to save completion.