BrightPath
Back to Course
Year 12 Coding

Object-Oriented Programming in Python

Master classes, objects, the __init__ constructor, methods, and attributes to build well-structured Python programs.

What is Object-Oriented Programming?

Object-Oriented Programming (OOP) is a programming paradigm that organises code around objects rather than functions and logic alone. An object bundles together data (attributes) and behaviour (methods) into a single unit.

In Python, you define a class as a blueprint and then create instances (objects) from that class. This promotes code reuse, modularity, and easier maintenance of large programs.

class Dog:
    """A simple Dog class."""

    def __init__(self, name, breed):
        self.name = name      # instance attribute
        self.breed = breed    # instance attribute

    def bark(self):
        return f"{self.name} says Woof!"

# Creating objects (instances)
my_dog = Dog("Buddy", "Labrador")
print(my_dog.bark())   # Buddy says Woof!

The __init__ method is called automatically when a new object is created. self refers to the current instance.

Attributes and Methods

Attributes store data about an object. They can be instance attributes (unique to each object) or class attributes (shared across all instances). Methods are functions defined inside a class that operate on the object's data.

class BankAccount:
    interest_rate = 0.05  # class attribute (shared)

    def __init__(self, owner, balance=0):
        self.owner = owner        # instance attribute
        self.balance = balance    # instance attribute

    def deposit(self, amount):
        self.balance += amount
        return f"Deposited ${amount}. New balance: ${self.balance}"

    def withdraw(self, amount):
        if amount > self.balance:
            return "Insufficient funds"
        self.balance -= amount
        return f"Withdrew ${amount}. Remaining: ${self.balance}"

    def apply_interest(self):
        self.balance += self.balance * BankAccount.interest_rate
        return f"Interest applied. New balance: ${self.balance:.2f}"

acc = BankAccount("Alice", 1000)
print(acc.deposit(500))        # Deposited $500. New balance: $1500
print(acc.apply_interest())    # Interest applied. New balance: $1575.00

Encapsulation and Privacy Conventions

Encapsulation means restricting direct access to some of an object's internal data. In Python, a single underscore prefix (_name) signals that an attribute is intended for internal use, while a double underscore (__name) triggers name mangling for stronger privacy.

class Student:
    def __init__(self, name, grade):
        self.name = name          # public
        self._grade = grade       # protected (convention)
        self.__student_id = id(self)  # private (name mangled)

    def get_id(self):
        """Getter method provides controlled access."""
        return self.__student_id

    @property
    def grade(self):
        """Property decorator for clean access."""
        return self._grade

    @grade.setter
    def grade(self, value):
        if 0 <= value <= 100:
            self._grade = value
        else:
            raise ValueError("Grade must be between 0 and 100")

s = Student("Maya", 95)
print(s.grade)      # 95 (uses property getter)
s.grade = 88        # uses property setter
print(s.get_id())   # controlled access to private attr

Key Vocabulary

Class

A blueprint or template that defines the structure and behaviour of objects. Defined with the class keyword.

Object / Instance

A concrete entity created from a class. Each instance has its own set of attribute values.

__init__

The constructor method that runs automatically when a new object is instantiated. Used to initialise attributes.

self

A reference to the current instance of the class. It must be the first parameter of every instance method.

Worked Examples

1

Create a Rectangle class with area and perimeter methods

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def area(self):
        return self.width * self.height

    def perimeter(self):
        return 2 * (self.width + self.height)

    def __str__(self):
        return f"Rectangle({self.width} x {self.height})"

r = Rectangle(5, 3)
print(r)              # Rectangle(5 x 3)
print(r.area())       # 15
print(r.perimeter())  # 16

Step 1: Define the class and __init__ to accept width and height.

Step 2: Create area() and perimeter() methods that compute using stored attributes.

Step 3: The __str__ dunder method gives a readable string when printing the object.

2

Build a Playlist class using a list attribute

class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []  # mutable default via __init__

    def add_song(self, song):
        self.songs.append(song)

    def remove_song(self, song):
        if song in self.songs:
            self.songs.remove(song)

    def show(self):
        print(f"Playlist: {self.name}")
        for i, song in enumerate(self.songs, 1):
            print(f"  {i}. {song}")

my_list = Playlist("Study Vibes")
my_list.add_song("Lo-fi Beats")
my_list.add_song("Chill Waves")
my_list.show()
# Playlist: Study Vibes
#   1. Lo-fi Beats
#   2. Chill Waves

Key point: Initialise mutable attributes (like lists) inside __init__, never as default parameter values, to avoid sharing across instances.

3

Implement a Counter with class methods and static methods

class Counter:
    total_counters = 0  # class attribute

    def __init__(self, start=0):
        self.value = start
        Counter.total_counters += 1

    def increment(self):
        self.value += 1

    @classmethod
    def get_total(cls):
        return f"Total counters created: {cls.total_counters}"

    @staticmethod
    def is_positive(n):
        return n > 0

c1 = Counter()
c2 = Counter(10)
c1.increment()
print(c1.value)              # 1
print(Counter.get_total())   # Total counters created: 2
print(Counter.is_positive(5))  # True

@classmethod receives the class (cls) instead of the instance. Useful for accessing class-level data.

@staticmethod does not receive the instance or class. It is a utility function that logically belongs to the class.

Knowledge Check

Select the correct answer for each question. Click "Check Answer" to see if you are right.

Question 1

What is the purpose of the __init__ method in a Python class?

Question 2

What does self refer to inside a class method?

Question 3

Which of the following correctly creates an instance of class Car with a make parameter?

Question 4

What is a class attribute in Python?

Question 5

What does the @property decorator allow you to do?

Key Concepts Summary

Year 8: Simulation Next: Python Inheritance