Pebellslearn by building
0%
RoadmapMatrixCatalogSandboxSign in
Objects  ›  Lesson

Polymorphism

Polymorphism 15 minOne call, many types — and a loop that never asks which
You're building a piece ofA bank account that can't go wrong
This piece — monthly_run(): Every product prices itself, so month end has no if/elif chain to edit.
Scenario Month end, and the bank charges every account its fee. Next quarter there will be a fourth product — and the billing loop must not need editing when it arrives.
Your task
Define Current, Savings and Student, each with monthly_fee() returning 5.0, 0.0 and 1.0. Write monthly_run(kinds) which builds the matching account for each name and returns the total fee, rounded to 2 decimals. Example: monthly_run(["current", "student"]) → 6.0.

Polymorphism

Month end. Every account gets charged its fee, and the bank now sells three products. Here is the loop most people write first:

python

It works. It is also the thing you will be editing forever: every new product means finding this chain — and the four others like it scattered through the codebase — and adding a branch. Miss one and the product silently bills nothing.


Move the answer into the type

Polymorphism is the fix: stop asking what something is, and let it answer for itself.

python

The loop has no branches left. Adding a fourth product is a new class and nothing else — the billing code never learns it exists.

One line of source, many pieces of code

a.monthly_fee() is a single line, but it is not a single function. Python looks the method up on the object in front of it, every time it runs:

step through it
1class Account:
2 def __init__(self, balance):
3 self.balance = balance
4 
5 def monthly_fee(self):
6 return 5.0
7 
8 def after_fee(self):
9 return self.balance - self.monthly_fee()
10 
11class Savings(Account):
12 def monthly_fee(self):
13 return 0.0
14 
15for a in [Account(100), Savings(100)]:
16 print(a.after_fee())

Watch after_fee. It is written once, on the parent, and it existed before Savings did — yet self.monthly_fee() finds the child's version. The parent calls down into code it has never heard of.

What does this print?
class Fee:
    def amount(self):
        return 5.0

    def receipt(self):
        return f"charged {self.amount()}"

class Free(Fee):
    def amount(self):
        return 0.0

print(Free().receipt())

No base class required

Notice what the billing loop never did: check a type, or demand a shared parent. Current, Savings and Student above inherit from nothing at all.

What does this print?
class Wallet:
    def monthly_fee(self):
        return 2.0

def total(items):
    return sum(i.monthly_fee() for i in items)

class Voucher:
    def monthly_fee(self):
        return -1.0

print(total([Wallet(), Voucher()]))

Inheritance is one way to guarantee the method exists. It is not the only one, and Python does not insist on it.

Type checks are the smell

Once behaviour lives on the type, an isinstance or type(...) test inside a loop is usually a bug waiting for its second subclass:

broken — fix it

A student is being billed nothing. Make it print 6.0.

The repaired loop is also the shorter one. That is the usual shape of this refactor: the branches don't move somewhere else, they stop existing.

You have been using it all along

len() has no idea what a string is. It asks.

python

Three unrelated types, one call, three different pieces of code underneath. Lesson 4 puts your own types on the receiving end of that.


🎯 Your turn

Define Current, Savings and Student, each with monthly_fee() returning 5.0, 0.0 and 1.0. Write monthly_run(kinds) which builds the matching account for each name and returns the total fee, rounded to two decimals:

  • monthly_run(["current", "savings"])5.0
  • monthly_run(["current", "student", "student"])7.0
  • monthly_run([])0

Hint — map the names to classes with a dict, then sum. The summing loop should contain no if at all.

Then press ▶ Run and hit ✓ Check. ✅

Practice — level up

0/5 solved

Solve each in its own editor — Run to try, Check to grade. Stuck? Reveal a Hint, or the full Solution + walkthrough.

Warm-upThe parent runs the child's code

Define Fee with amount() returning 5.0 and receipt() returning f"charged {self.amount()}". Define Free(Fee) overriding only amount() to return 0.0. Write receipt_for(kind) returning the receipt of a Free when kind is "free", otherwise of a Fee.

polymorphismoverride
DrillA loop with no branches

Define Current and Savings, each with monthly_fee() returning 5.0 and 0.0. Write fee_list(kinds) returning the fee of each named account, in order. The loop that collects the fees must not test the kind.

polymorphismcomprehension
BuildTwo strangers, one call

Define Email and SMS. Neither inherits from anything. Each has send(msg) returning "email: <msg>" and "sms: <msg>". Write broadcast(msg) returning what both return, as a list, by looping over one of each.

polymorphismduck-typing
BossRetire the if/elif chain

Define Standard, Premium and Student, each with rate() returning 0.0, 0.02 and 0.01. Write charges(kinds, amount) returning amount * rate for each named product, each rounded to 2 decimals. Choose the class through a dict — there must be no if anywhere in the function.

polymorphismdispatchdictionaries
CapstoneA statement that renders itself

Define Deposit, Withdrawal and Fee, each built from an amount, each with line() returning "+50" / "-20" / "fee 2" and effect() returning what it does to the balance (+amount, -amount, -amount). Write statement(entries, start) where entries look like [["deposit", 50], ["fee", 2]], returning {"lines": [...], "balance": ...} with the balance rounded to 2 decimals.

polymorphismdispatchloop
monthly_run.py
Call the function you wrote — just like your app's frontend would. Edit the inputs and hit Call.
monthly_run(kinds) → floatCharge each account its own fee; return the total.
try:
returned
Press Run to execute your code.
Press Check to run the tests for this lesson.