All subjects · Computer Science · WCP04 Advanced Practical Programming and Problem-solving

Object-oriented programming: classes, objects and encapsulation

Edexcel IAL Computer Science · WCP04 Advanced Practical Programming and Problem-solving

Object-oriented programming (OOP) bundles data and the code that works on it into objects. A class is the blueprint; each object (instance) made from it has its own attribute values.

Practise this in the app: instant marking and a fix for every mistake. Free, no email needed to start.

Practise this lesson

Classes, attributes and methods

Encapsulation

Encapsulation keeps an object's data inside it and controls access through methods, so the data cannot be put into an invalid state.

class BankAccount:
    interest_rate = 0.02                  # class attribute, shared by all accounts

    def __init__(self, owner, balance=0):
        self.owner = owner                # public attribute
        self._branch = "Main"             # protected by convention
        self.__balance = balance          # private: name-mangled

    def get_balance(self):                # getter
        return self.__balance

    def set_balance(self, amount):        # setter with validation
        if amount < 0:
            raise ValueError("balance cannot be negative")
        self.__balance = amount

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("deposit must be positive")
        self.__balance = self.__balance + amount

    def add_interest(self):
        self.__balance = round(self.__balance * (1 + BankAccount.interest_rate), 2)

    def __str__(self):
        return f"{self.owner}: {self.__balance:.2f}"

Using the class

# BankAccount class as above

a = BankAccount("Ana", 100)
b = BankAccount("Ben")
a.deposit(50)
a.add_interest()
print(a, "|", b, "|", a.get_balance())
try:
    print(a.__balance)
except AttributeError:
    print("private attribute hidden")
try:
    b.set_balance(-5)
except ValueError as e:
    print("rejected:", e)

Output:

Ana: 153.00 | Ben: 0.00 | 153.0
private attribute hidden
rejected: balance cannot be negative

Each object keeps its own balance; the deposit and setter refuse invalid values, which is the point of encapsulation.

Worked example

Write a Rectangle class with private width and height, a constructor that rejects non-positive sizes, and an area method.

  1. ```python class Rectangle: def __init__(self, width, height): if width <= 0 or height <= 0: raise ValueError("sides must be positive") self.__width = width self.__height = height def area(self): return self.__width * self.__height def get_width(self): return self.__width r = Rectangle(4, 2.5) print(r.area(), r.get_width()) ```
  2. Output: ``` 10.0 4 ```

Answer: Validation in the constructor means no Rectangle can ever exist with an invalid size; the private attributes can only be read through methods.

Key idea: A class is a blueprint; __init__ sets attributes; self is the current object; methods are functions in the class; class attributes are shared. Encapsulation: _protected by convention, __private by name mangling; getters and setters control access and validate.

Check yourself

  1. The BankAccount class from the lesson is included in the code below. What does this print? ```python class BankAccount: interest_rate = 0.02 # class attribute, shared by all accounts def __init__(self, owner, balance=0): self.owner = owner # public attribute self._branch = "Main" # protected by convention self.__balance = balance # private: name-mangled def get_balance(self): # getter return self.__balance def set_balance(self, amount): # setter with validation if amount < 0: raise ValueError("balance cannot be negative") self.__balance = amount def deposit(self, amount): if amount <= 0: raise ValueError("deposit must be positive") self.__balance = self.__balance + amount def add_interest(self): self.__balance = round(self.__balance * (1 + BankAccount.interest_rate), 2) def __str__(self): return f"{self.owner}: {self.__balance:.2f}" x = BankAccount("Kai", 20) y = BankAccount("Lu", 20) x.deposit(30) print(x.get_balance(), y.get_balance()) ```
    Show answer

    50 20. Each object has its own private balance, so only x changes. Output: `50 20`

  2. The BankAccount class from the lesson is included in the code below. What does this print? ```python class BankAccount: interest_rate = 0.02 # class attribute, shared by all accounts def __init__(self, owner, balance=0): self.owner = owner # public attribute self._branch = "Main" # protected by convention self.__balance = balance # private: name-mangled def get_balance(self): # getter return self.__balance def set_balance(self, amount): # setter with validation if amount < 0: raise ValueError("balance cannot be negative") self.__balance = amount def deposit(self, amount): if amount <= 0: raise ValueError("deposit must be positive") self.__balance = self.__balance + amount def add_interest(self): self.__balance = round(self.__balance * (1 + BankAccount.interest_rate), 2) def __str__(self): return f"{self.owner}: {self.__balance:.2f}" acc = BankAccount("Mo", 200) acc.add_interest() print(acc) ```
    Show answer

    Mo: 204.00. 200 x 1.02 = 204, and __str__ formats it to 2 decimal places. Output: `Mo: 204.00`

  3. The BankAccount class from the lesson is included in the code below. What does this print? ```python class BankAccount: interest_rate = 0.02 # class attribute, shared by all accounts def __init__(self, owner, balance=0): self.owner = owner # public attribute self._branch = "Main" # protected by convention self.__balance = balance # private: name-mangled def get_balance(self): # getter return self.__balance def set_balance(self, amount): # setter with validation if amount < 0: raise ValueError("balance cannot be negative") self.__balance = amount def deposit(self, amount): if amount <= 0: raise ValueError("deposit must be positive") self.__balance = self.__balance + amount def add_interest(self): self.__balance = round(self.__balance * (1 + BankAccount.interest_rate), 2) def __str__(self): return f"{self.owner}: {self.__balance:.2f}" BankAccount.interest_rate = 0.1 acc = BankAccount("Ola", 50) acc.add_interest() print(acc.get_balance()) ```
    Show answer

    55.0. Changing the class attribute affects every account: 50 x 1.1 = 55.0. Output: `55.0`

  4. Which exception does this program raise? ```python class BankAccount: interest_rate = 0.02 # class attribute, shared by all accounts def __init__(self, owner, balance=0): self.owner = owner # public attribute self._branch = "Main" # protected by convention self.__balance = balance # private: name-mangled def get_balance(self): # getter return self.__balance def set_balance(self, amount): # setter with validation if amount < 0: raise ValueError("balance cannot be negative") self.__balance = amount def deposit(self, amount): if amount <= 0: raise ValueError("deposit must be positive") self.__balance = self.__balance + amount def add_interest(self): self.__balance = round(self.__balance * (1 + BankAccount.interest_rate), 2) def __str__(self): return f"{self.owner}: {self.__balance:.2f}" acc = BankAccount("Pat", 10) print(acc.__balance) ```
    • NameError
    • AttributeError
    • KeyError
    • ValueError
    Show answer

    AttributeError. Inside the class __balance is renamed _BankAccount__balance, so the name acc.__balance does not exist outside: AttributeError.

  5. What is the main purpose of encapsulation?
    • To make programs run faster
    • To let one class reuse another class's code
    • To allow a method to behave differently in different classes
    • To stop an object's data being changed in invalid ways by controlling access through methods
    Show answer

    To stop an object's data being changed in invalid ways by controlling access through methods. Data is hidden inside the object and changed only through validating methods.

  6. What does `self` refer to inside a method?
    • The class itself
    • The particular object the method was called on
    • The first argument passed by the caller
    • The module the class is in
    Show answer

    The particular object the method was called on. In `a.deposit(50)`, self is a, and 50 is the amount.

  7. Why does a setter method usually contain an if statement?
    • To make the attribute public
    • To create a new object
    • To print the old value
    • To validate the new value before storing it
    Show answer

    To validate the new value before storing it. Validation is what makes a setter better than changing the attribute directly.

Practise with instant feedback

Sources

Last updated 8 November 2026. Spotted a mistake? Tell us.