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.
Classes, attributes and methods
- A class defines the attributes (data) and methods (functions) that its objects will have.
__init__is the constructor: it runs when an object is created and sets the starting attribute values.selfrefers to the object being created or used.- Instance attributes (
self.owner) belong to one object; a class attribute (interest_ratebelow) is shared by all objects of the class. - A method is a function defined in the class; call it on an object:
a.deposit(50). __str__returns the text thatprint(obj)shows.- An instance object is created by calling the class:
a = BankAccount("Ana", 100).
Encapsulation
Encapsulation keeps an object's data inside it and controls access through methods, so the data cannot be put into an invalid state.
- A single underscore,
_branch, marks an attribute as protected: by convention, only the class and its subclasses should use it. - A double underscore,
__balance, makes it private: Python renames (mangles) it to_BankAccount__balance, soa.__balanceoutside the class raises AttributeError. - Getters return a private value; setters change it after validating the new value.
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 negativeEach 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.
- ```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()) ```
- 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.
Check yourself
- 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`
- 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`
- 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`
- 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.
- 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.
- 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.
- 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.
Sources
- Pearson Edexcel International Advanced Subsidiary/Advanced Level in Computer Science specification, Issue 1 (March 2026)
- Pearson Edexcel IAL Computer Science Unit 4 Programming Language Subset (PLS)
- Pearson Edexcel IAL Computer Science sample assessment materials (Unit 3 and Unit 4)
Last updated 8 November 2026. Spotted a mistake? Tell us.