runtime
133 / 250
lesson 133 Inheritance & Composition · advanced

Polymorphism: one call, many classes

When several classes offer a method with the same name and meaning, code can call it without knowing which class it has. Each object runs its own version, and new kinds can be added without touching the caller.

You'll be able to
  • Give several classes a method with the same name, parameters and meaning
  • Write code that calls that method without checking which class it has
  • Add a new class that works with existing code without changing that code
The idea

Delivery costs work differently for standard, express and collect-in-store. One way to write the quote is a chain: if isinstance(option, StandardDelivery): … elif isinstance(option, ExpressDelivery): …. Every new delivery option means editing that chain, and every other place with a similar chain.

The alternative: each class has a method with the same name and meaning, cost(weight_kg). The caller just calls option.cost(weight), and Python runs the method of whichever class the object belongs to. This is polymorphism: one call, many forms.

The classes don't even need a common parent. Python only needs the object to have the method when it is called (this is called *duck typing*: if it has cost, it can be quoted).

Run each example to see what it prints. Running examples doesn't count toward progress.

example-1.py · Three ways to deliver
class StandardDelivery:
    def cost(self, weight_kg):
        return 3.5 if weight_kg <= 2 else 3.5 + (weight_kg - 2) * 1.25

class ExpressDelivery:
    def cost(self, weight_kg):
        return 8.0 + weight_kg * 0.5

class CollectInStore:
    def cost(self, weight_kg):
        return 0.0

def quotes(options, weight_kg):
    for option in options:
        print(type(option).__name__, round(option.cost(weight_kg), 2))

quotes([StandardDelivery(), ExpressDelivery(), CollectInStore()], 4.0)
example-2.py · Notifications through any channel
class EmailChannel:
    def __init__(self, address):
        self.address = address

    def send(self, message):
        return f"email to {self.address}: {message}"

class SmsChannel:
    def __init__(self, number):
        self.number = number

    def send(self, message):
        return f"sms to {self.number}: {message[:20]}"

class WebhookChannel:
    def __init__(self, url):
        self.url = url

    def send(self, message):
        return f"POST {self.url} {{\"text\": \"{message}\"}}"

def notify(channels, message):
    receipts = []
    for channel in channels:
        receipts.append(channel.send(message))
    return receipts

channels = [EmailChannel("nora_garcia@fastmail.com"), SmsChannel("+44 7700 900123"), WebhookChannel("https://hooks.example.com/orders")]
for receipt in notify(channels, "Order O100282 has shipped"):
    print(receipt)
example-3.py · A class with a different method name
class EmailChannel:
    def send(self, message):
        return "email: " + message

class PrinterChannel:
    def print_message(self, message):
        return "printed: " + message

for channel in [EmailChannel(), PrinterChannel()]:
    print(channel.send("Order O100282 has shipped"))

When to use it: whenever you find yourself writing if isinstance(x, A) … elif isinstance(x, B) … to decide what to do. Move each branch into a method of its class.

Mistakes to avoid: methods with the same name but different parameters or return types (cost() returning a number in one class and text in another); one class printing where the others return; and keeping a type check in the caller "just in case", which defeats the point.

Review
  • Classes that offer the same method (same name, parameters and meaning) can be used interchangeably.
  • The caller calls the method; each object runs its own version. No isinstance chain is needed.
  • New kinds are added as new classes, without editing the code that uses them.
  • Next: objects that hold other objects and hand work to them.
Practise this

Loading your progress…

To master this lesson, solve both challenges without hints or the answer, and pass the concept check.

Polymorphism: one call, many classes — Inheritance & Composition · Runtime