Simple Programs to demonstrate Classes

Next Topic(s):

Created:
4th of November 2025
05:42:01 PM
Modified:
4th of November 2025
05:44:35 PM

Exercises: Classes in Python (Simple Practice)

These ten short exercises reinforce class basics using familiar daily objects before moving into civil engineering elements—walls, doors, and windows. Each task includes a minimal starter you can run and extend. Use British English in comments and clear, descriptive names.

💡

Tip: In Java you’d speak of fields and methods. In Python, the data on an object are attributes and the behaviours are still methods. The constructor is __init__; self plays the role of Java’s this.

Exercise 01 — Student (Attributes & Methods)

Filename: ex01_student.py

  • Create a class Student with attributes name (str) and enrolled (bool, default True).
  • Add methods suspend() and reinstate() that flip enrolled.
  • Implement __str__ to print Student(name="Ada", enrolled=True).
class Student:
    def __init__(self, name: str, enrolled: bool = True):
        # TODO: store arguments on self
        pass

    def suspend(self):
        # TODO: set enrolled to False
        pass

    def reinstate(self):
        # TODO: set enrolled to True
        pass

    def __str__(self) -> str:
        # TODO: return a friendly string
        return ""


if __name__ == "__main__":
    s = Student("Ada")
    print(s)
    s.suspend()
    print(s)
    s.reinstate()
    print(s)

Exercise 02 — Cup (State Changes)

Filename: ex02_cup.py

  • Create Cup with colour, capacity_ml, and contents_ml (start at 0).
  • Add fill(amount_ml) (do not exceed capacity) and empty().
  • Print the contents after each action.
class Cup:
    def __init__(self, colour: str, capacity_ml: int):
        # TODO
        pass

    def fill(self, amount_ml: int):
        # TODO: increase contents_ml without exceeding capacity
        pass

    def empty(self):
        # TODO: set contents_ml to 0
        pass


if __name__ == "__main__":
    mug = Cup("blue", 300)
    mug.fill(120)
    print(mug.contents_ml)  # expected: 120
    mug.fill(300)
    print(mug.contents_ml)  # expected: 300
    mug.empty()
    print(mug.contents_ml)  # expected: 0

Exercise 03 — Opening (Geometry & Units)

Filename: ex03_opening.py

  • Create Opening with width_mm and height_mm.
  • Add method area_m2() that returns area in square metres.
  • Round to two decimals when printing.
class Opening:
    def __init__(self, width_mm: int, height_mm: int):
        # TODO
        pass

    def area_m2(self) -> float:
        # TODO: convert mm to m then compute area
        return 0.0


if __name__ == "__main__":
    o = Opening(900, 2100)  # typical door leaf
    print(f"Opening area (m²): {o.area_m2():.2f}")

Exercise 04 — Door (Inheritance)

Filename: ex04_door.py

  • Subclass Door from Opening.
  • Add attributes material (str) and fire_rating_min (int, default 0).
  • Add method is_fire_door() returning True if rating ≥ 30.
from ex03_opening import Opening  # or paste Opening here

class Door(Opening):
    def __init__(self, width_mm: int, height_mm: int, material: str, fire_rating_min: int = 0):
        # TODO: call super().__init__(...) and store extras
        pass

    def is_fire_door(self) -> bool:
        # TODO: return True if rating meets threshold
        return False


if __name__ == "__main__":
    d = Door(900, 2100, "timber", 60)
    print(d.area_m2())
    print(d.is_fire_door())  # expected: True

Exercise 05 — Window (Property)

Filename: ex05_window.py

  • Subclass Window from Opening.
  • Add frame_thickness_mm (int, default 60) and glass_type (str).
  • Expose a computed @property named clear_opening_m2 that subtracts the frame on all sides.
from ex03_opening import Opening  # or paste Opening here

class Window(Opening):
    def __init__(self, width_mm: int, height_mm: int, glass_type: str, frame_thickness_mm: int = 60):
        # TODO
        pass

    @property
    def clear_opening_m2(self) -> float:
        # TODO: compute daylight opening area
        return 0.0


if __name__ == "__main__":
    w = Window(1200, 1200, "double-glazed", 70)
    print(f"Clear opening (m²): {w.clear_opening_m2:.3f}")

Exercise 06 — Wall (Composition)

Filename: ex06_wall.py

  • Create Wall with width_mm, height_mm, and a list openings.
  • Add add_opening(opening), gross_area_m2(), openings_area_m2(), and net_area_m2().
  • Test with at least one Door and one Window.
from ex04_door import Door
from ex05_window import Window

class Wall:
    def __init__(self, width_mm: int, height_mm: int):
        # TODO
        pass

    def add_opening(self, opening):
        # TODO: append to the list
        pass

    def gross_area_m2(self) -> float:
        # TODO
        return 0.0

    def openings_area_m2(self) -> float:
        # TODO
        return 0.0

    def net_area_m2(self) -> float:
        # TODO
        return 0.0


if __name__ == "__main__":
    wall = Wall(4000, 3000)
    wall.add_opening(Door(900, 2100, "timber", 60))
    wall.add_opening(Window(1200, 1200, "double-glazed"))
    print(f"Net area (m²): {wall.net_area_m2():.2f}")

Exercise 07 — Wall Schedule (String Representation)

Filename: ex07_wall_schedule.py

  • Extend Wall to implement __str__, returning a multi-line summary:
    • Gross area, openings area, net area
    • Count of doors and windows
from ex06_wall import Wall
from ex04_door import Door
from ex05_window import Window

class SchedulableWall(Wall):
    def __str__(self) -> str:
        # TODO: build and return a readable summary
        return ""


if __name__ == "__main__":
    w = SchedulableWall(5000, 3000)
    w.add_opening(Door(900, 2100, "steel", 120))
    w.add_opening(Window(1500, 1200, "laminated"))
    print(w)  # expected: multi-line report

Exercise 08 — Building (Collection of Walls)

Filename: ex08_building.py

  • Create Building with a list of walls.
  • Add methods add_wall(wall), total_net_area_m2(), and largest_wall() (return the wall with the greatest net area).
from ex06_wall import Wall
from ex04_door import Door
from ex05_window import Window

class Building:
    def __init__(self, name: str):
        # TODO
        pass

    def add_wall(self, wall: Wall):
        # TODO
        pass

    def total_net_area_m2(self) -> float:
        # TODO
        return 0.0

    def largest_wall(self) -> Wall | None:
        # TODO: return the wall with max net area (or None)
        return None


if __name__ == "__main__":
    b = Building("Block A")
    w1 = Wall(4000, 3000); w1.add_opening(Window(1200, 1200, "double-glazed"))
    w2 = Wall(6000, 3000); w2.add_opening(Door(1000, 2100, "timber"))
    b.add_wall(w1); b.add_wall(w2)
    print(f"Total net area (m²): {b.total_net_area_m2():.2f}")
    print(b.largest_wall())

Exercise 09 — Door Factory (Class Method)

Filename: ex09_door_factory.py

  • Add a @classmethod standard on Door that constructs common sizes by code:
    • "D826" → 826 mm × 2040 mm
    • "D900" → 900 mm × 2100 mm
  • Unknown codes should raise ValueError.
from ex04_door import Door

class DoorWithFactory(Door):
    @classmethod
    def standard(cls, code: str, material: str = "timber", fire_rating_min: int = 0):
        # TODO: map code to (width_mm, height_mm) and return cls(...)
        # Hint: use a dict
        pass


if __name__ == "__main__":
    d = DoorWithFactory.standard("D900", material="steel", fire_rating_min=60)
    print(d.width_mm, d.height_mm)

Exercise 10 — Geometry Utils (Static Method)

Filename: ex10_geometry_utils.py

  • Create GeometryUtils with a @staticmethod mm_to_m(value_mm) returning metres as float.
  • Refactor Opening.area_m2 to use this utility.
class GeometryUtils:
    @staticmethod
    def mm_to_m(value_mm: float) -> float:
        # TODO
        return 0.0


# Optional: demonstrate reuse with Opening
class Opening:
    def __init__(self, width_mm: int, height_mm: int):
        self.width_mm = width_mm
        self.height_mm = height_mm

    def area_m2(self) -> float:
        w = GeometryUtils.mm_to_m(self.width_mm)
        h = GeometryUtils.mm_to_m(self.height_mm)
        return w * h


if __name__ == "__main__":
    o = Opening(1000, 1000)
    print(o.area_m2())  # expected: 1.0
💡

Hint for all exercises: Keep classes small and focused. Prefer composition (has-a) before inheritance (is-a). Write tiny tests in if __name__ == "__main__": to check behaviour early.

Next Steps

  • Create a simple “solutions” page mirroring these headings.
  • Add unit tests with pytest later to formalise checks.