Classes in Python (An Introduction)
Next Topic(s):
Created:
4th of November 2025
05:35:07 PM
Modified:
4th of November 2025
05:42:58 PM
Classes in Python (An Introduction)
Objects are everywhere: a mug, a door, a window. Each has properties and behaviour. In programming, a class is a blueprint that describes such objects. Python uses classes to model real-world entities clearly and flexibly, helping us organise code and reason about systems—from day-to-day life to civil engineering models.
What Is a Class?
A class defines the attributes (data) and methods (behaviour) that its objects (instances) will have. In Java you might speak about fields and methods; in Python we say attributes and methods. The idea is the same: bundle related state with the actions that operate on that state.
Daily Example: A Simple Cup
# Defining a simple class
class Cup:
def __init__(self, colour, capacity_ml):
self.colour = colour # attribute (Java: field/member variable)
self.capacity_ml = capacity_ml
self.contents_ml = 0
def fill(self, amount_ml):
space = self.capacity_ml - self.contents_ml
self.contents_ml += min(amount_ml, space)
return self.contents_ml
def empty(self):
self.contents_ml = 0
# Using the class (creating instances/objects)
mug = Cup("blue", 300)
mug.fill(120)
print(mug.contents_ml) # 120
Explanation: Cup is the blueprint. mug is an instance with its own state. The __init__ method is Python’s constructor (Java’s constructor equivalent). Attributes such as colour and capacity_ml are bound to each instance via self, which plays a role similar to Java’s this.
Tip: Use clear, domain-specific names for attributes and methods. The class should read like a small, honest description of the real object you are modelling.
From Everyday Objects to Civil Engineering
In buildings, walls contain openings. Doors and windows are both openings with width and height, yet each has extra details: material, fire rating, glazing type, and so on. This is a perfect use-case for inheritance: define a general Opening and extend it for Door and Window. A Wall then contains a set of openings—this is composition.
Example: Modelling Walls, Doors, and Windows
# Base class (general opening in a wall)
class Opening:
def __init__(self, width_mm, height_mm):
self.width_mm = width_mm
self.height_mm = height_mm
def area_m2(self):
return (self.width_mm / 1000) * (self.height_mm / 1000)
# Subclasses extend the base opening with extra attributes/behaviour
class Door(Opening):
def __init__(self, width_mm, height_mm, material, fire_rating_min=0):
super().__init__(width_mm, height_mm)
self.material = material
self.fire_rating_min = fire_rating_min
class Window(Opening):
def __init__(self, width_mm, height_mm, glass_type, is_operable=True):
super().__init__(width_mm, height_mm)
self.glass_type = glass_type
self.is_operable = is_operable
# Composition: a wall that aggregates openings
class Wall:
def __init__(self, width_mm, height_mm):
self.width_mm = width_mm
self.height_mm = height_mm
self.openings = [] # list of Opening (Door/Window)
def add_opening(self, opening):
self.openings.append(opening)
def gross_area_m2(self):
return (self.width_mm / 1000) * (self.height_mm / 1000)
def openings_area_m2(self):
return sum(o.area_m2() for o in self.openings)
def net_area_m2(self):
return max(0.0, self.gross_area_m2() - self.openings_area_m2())
# Sample usage
wall = Wall(4000, 3000) # 4.0 m x 3.0 m
door = Door(900, 2100, "timber", 60) # 0.9 m x 2.1 m fire door
win = Window(1200, 1200, "double-glazed", True)
wall.add_opening(door)
wall.add_opening(win)
print(f"Gross area (m²): {wall.gross_area_m2():.2f}")
print(f"Openings area (m²): {wall.openings_area_m2():.2f}")
print(f"Net area for finishes (m²): {wall.net_area_m2():.2f}")
Explanation: Opening captures common geometry. Door and Window inherit and extend this behaviour. Wall composes openings and computes gross, openings, and net areas—useful for estimating finishes, load checks on non-structural elements, and coordination with schedules.
Tip: Prefer composition (has-a) for assembling systems and inheritance (is-a) for specialisation. Keep the base class small; only place what is truly shared there.
Mapping Terms: Java ↔ Python
Different words, shared ideas. Here is a quick mapping between common Java and Python class terminology.
| Concept | Java Term | Python Term | Usage / Example |
|---|---|---|---|
| Blueprint | class |
class |
Define once, instantiate many times. |
| Thing created | object / instance | object / instance | mug = Cup(...) |
| Data on the object | field (member variable) | attribute (instance attribute) | self.capacity_ml |
| Behaviour | method | method | def fill(self, ...) |
| Constructor | ClassName(...) |
__init__(self, ...) |
Initialise attributes on creation. |
| Static behaviour | static method |
@staticmethod |
No access to self. |
| Class-level behaviour | static method / field |
@classmethod / class attribute |
Acts on the class, not a single instance. |
| Inheritance | extends |
class Child(Base) |
Specialise or reuse behaviour. |
| Interfaces & contracts | interface |
Abstract Base Class (abc.ABC) |
Optional in Python; duck typing is common. |
| Access control | public/private/protected |
Conventions: _internal, __name |
Name-mangling for __name; rely on discipline. |
| Properties | getters/setters | @property |
Computed attributes with simple syntax. |
Trivia: Python emphasises “consenting adults” over strict access modifiers. Instead of hard enforcement, code style and naming conventions guide responsible use.
Adding Calculated Properties with @property
# A window with a computed daylight opening area (example property)
class Window(Opening):
def __init__(self, width_mm, height_mm, glass_type, frame_thickness_mm=60):
super().__init__(width_mm, height_mm)
self.glass_type = glass_type
self.frame_thickness_mm = frame_thickness_mm
@property
def clear_opening_m2(self):
w = max(0, self.width_mm - 2 * self.frame_thickness_mm)
h = max(0, self.height_mm - 2 * self.frame_thickness_mm)
return (w / 1000) * (h / 1000)
win = Window(1200, 1200, "double-glazed", 70)
print(f"Clear opening (m²): {win.clear_opening_m2:.3f}")
Explanation: @property lets us expose a calculated value as if it were a simple attribute. In Java we would typically write getClearOpening(); Python keeps the call-site tidy while allowing validation or computation inside the class.
Common Pitfalls
- Forgetting
selfin method definitions: Every instance method must acceptselfas the first parameter. - Accidentally sharing state: Do not store per-instance data as class attributes. Use
self.attributein__init__. - Overusing inheritance early: Start with composition. Introduce inheritance only when you see genuine shared behaviour.
- Confusing attributes with properties: Switch to
@propertywhen validation or on-the-fly calculation is needed, but keep it simple.
Key Takeaways
- Use classes to group related data (attributes/fields) and behaviour (methods).
- Model civil elements naturally: walls have openings; doors and windows are openings.
- Map your Java mental model to Python terms quickly with the terminology table.
- Prefer composition; add inheritance when specialisation is obvious.
Tip: For data-heavy classes (schedules, catalogues), consider dataclasses to reduce boilerplate while keeping types explicit.