Python Composition · Examples
Object-Oriented Python

Python Composition Examples

Short, runnable programs that show python composition: has-a vs is-a in action. Read the explanation, press Run, then change the code and try your own ideas. New to the topic? Start with the Python Composition: Has-A vs Is-A lesson.

1A library that has books

The Library keeps a list of Book objects. Its methods search and update those objects instead of storing loose data of its own.

A library that has books
class Book:
    def __init__(self, title, author):
        self.title = title
        self.author = author
        self.available = True

class Library:
    def __init__(self):
        self.books = []

    def add(self, book):
        self.books.append(book)

    def by_author(self, author):
        return [book.title for book in self.books if book.author == author]

    def lend(self, title):
        for book in self.books:
            if book.title == title and book.available:
                book.available = False
                return True
        return False

city = Library()
city.add(Book("Emma", "Austen"))
city.add(Book("Persuasion", "Austen"))
city.add(Book("Dracula", "Stoker"))
print(city.by_author("Austen"))
print(city.lend("Emma"), city.lend("Emma"))
Expected output['Emma', 'Persuasion']
True False

2Swapping a part at runtime

Alert only needs a channel with a send() method. Two unrelated classes qualify, and the channel can be replaced while the program runs.

Swapping a part at runtime
class EmailChannel:
    def send(self, text):
        return f"email: {text}"

class SmsChannel:
    def send(self, text):
        return f"sms: {text[:20]}"

class Alert:
    def __init__(self, channel):
        self.channel = channel

    def raise_alarm(self, reason):
        return self.channel.send(f"ALERT {reason}")

alert = Alert(EmailChannel())
print(alert.raise_alarm("disk almost full"))
alert.channel = SmsChannel()
print(alert.raise_alarm("disk almost full"))
Expected outputemail: ALERT disk almost full
sms: ALERT disk almost fu

3Testing with a fake part

Composition makes testing easy. Instead of a real sensor, the thermostat receives a FakeSensor that always returns the number you choose, so the result is predictable.

Testing with a fake part
class Thermostat:
    def __init__(self, sensor, target):
        self.sensor = sensor
        self.target = target

    def action(self):
        temp = self.sensor.read()
        if temp < self.target - 1:
            return "heat on"
        if temp > self.target + 1:
            return "cool on"
        return "idle"

class FakeSensor:
    def __init__(self, value):
        self.value = value

    def read(self):
        return self.value

for reading in (16, 21, 27):
    print(reading, Thermostat(FakeSensor(reading), 21).action())
Expected output16 heat on
21 idle
27 cool on

4Is-a and has-a together

Employee inherits from Person because an employee is a person. It holds an Address object because an employee has an address.

Is-a and has-a together
class Person:
    def __init__(self, name):
        self.name = name

class Address:
    def __init__(self, city, country):
        self.city = city
        self.country = country

    def __str__(self):
        return f"{self.city}, {self.country}"

class Employee(Person):
    def __init__(self, name, address):
        super().__init__(name)
        self.address = address

staff = Employee("Asha", Address("Pune", "India"))
print(staff.name, "lives in", staff.address)
print(isinstance(staff, Person), isinstance(staff, Address))
Expected outputAsha lives in Pune, India
True False

5Parts inside parts

A Pizza has a crust and a list of toppings. Both are the same small Ingredient class, and the pizza's price is built by asking each part for its own price.

Parts inside parts
class Ingredient:
    def __init__(self, name, price):
        self.name = name
        self.price = price

class Pizza:
    def __init__(self, crust):
        self.crust = crust
        self.toppings = []

    def add(self, topping):
        self.toppings.append(topping)

    def price(self):
        return self.crust.price + sum(t.price for t in self.toppings)

pizza = Pizza(Ingredient("thin crust", 8.0))
pizza.add(Ingredient("olives", 1.5))
pizza.add(Ingredient("mushrooms", 2.0))
names = [pizza.crust.name] + [t.name for t in pizza.toppings]
print(", ".join(names))
print("Price:", pizza.price())
Expected outputthin crust, olives, mushrooms
Price: 11.5

Practise next

Python Composition: Has-A vs Is-A exercises → Back to the lesson

Python Inheritance examplesPython Polymorphism and Duck Typing examplesPython Multiple Inheritance and MRO examples