Classes, Objects, and Encapsulation
You can read this note directly if you know Python variables and functions.
Object-oriented programming adds one central idea: combine the data describing an entity with the methods that operate on that data.
The Beginner Problem OOP Solves
Suppose a program stores student marks using separate variables:
name1 = "Aisha"
mark1 = 72
name2 = "Bo"
mark2 = 45The program must keep track of which name belongs to which mark. As the number of students and properties grows, the data becomes difficult to organise.
An object keeps the related values together:
s1 = Student("Aisha", 72)
s2 = Student("Bo", 45)Now:
s1represents one student;s2represents another student;- each object stores its own values;
- the class can define behaviour shared by all students.
Class Versus Object
A class is a definition or blueprint describing the attributes and methods of a type of object.
An object is a particular instance created from that class.
Caption: Instantiating the Student class creates separate objects that share the same design but store different attribute values.
class Student:
def __init__(self, name, mark):
self.name = name
self.mark = mark
def is_pass(self):
return self.mark >= 50Creating objects from the class is called instantiation:
s1 = Student("Aisha", 72)
s2 = Student("Bo", 45)| Object | name | mark | is_pass() |
|---|---|---|---|
s1 | "Aisha" | 72 | True |
s2 | "Bo" | 45 | False |
The objects share the same class design, but each object has its own state.
Attributes and Methods
| Term | Meaning | Example |
|---|---|---|
| attribute | data stored for an object or class | self.mark |
| method | a function defined inside a class | is_pass() |
| instance | a particular object created from a class | s1 |
| instantiation | the process of creating an object | Student("Aisha", 72) |
A method can read or change the attributes of the current object.
class Counter:
def __init__(self):
self.value = 0
def increase(self):
self.value = self.value + 1Calling counter.increase() changes the state stored in that particular object.
__init__() and self
At H2 Computing level, __init__() is commonly described as the constructor because it is used to initialise a newly created object.
def __init__(self, name, mark):
self.name = name
self.mark = markThe parameter self refers to the current object.
Read:
self.name = nameas:
store the parameter name in the name attribute of this objectWhen an instance method is called using dot notation, Python supplies the object as self.
| Method call | Object used as self | Attribute read |
|---|---|---|
s1.is_pass() | s1 | s1.mark |
s2.is_pass() | s2 | s2.mark |
This is why self appears in the method definition but is not normally written as an argument in the call.
Local variable versus instance attribute
class Student:
def __init__(self, name):
name = nameThis does not create a lasting object attribute. The variable name is only local to the method.
The correct form is:
class Student:
def __init__(self, name):
self.name = nameThe prefix self. attaches the value to the object.
Displaying an Object
Without a suitable string method, printing an object may show a technical representation rather than useful information.
class Student:
def __init__(self, name, mark):
self.name = name
self.mark = mark
def __str__(self):
return f"{self.name}: {self.mark}"s1 = Student("Aisha", 72)
print(s1)Output:
Aisha: 72__str__() is not one of the named conceptual outcomes in section 2.5, but it is a useful beginner-level Python method for producing readable object output.
Class Diagrams
Caption: Class diagrams show a class name, attributes, and methods.
A simple class diagram contains three sections:
- class name;
- attributes;
- methods.
For example:
Student
----------------
- name
- mark
----------------
+ is_pass()
+ set_mark(mark)The symbols commonly mean:
| Symbol | Meaning |
|---|---|
- | private or internal member |
+ | public member |
A class diagram describes the class design. It does not show the different values stored in every object.
Instance Attributes and Class Attributes
An instance attribute belongs to one object.
self.name = nameDifferent objects may store different values:
s1.name = "Aisha"
s2.name = "Bo"A class attribute belongs to the class and is shared through the class.
class Student:
count = 0
def __init__(self, name):
self.name = name
Student.count = Student.count + 1After creating three students, Student.count is 3.
| Feature | Instance attribute | Class attribute |
|---|---|---|
| belongs to | one object | the class |
| typical access | self.name or s1.name | Student.count |
| same value for all objects? | not necessarily | shared unless shadowed |
| suitable use | object-specific state | shared count or setting |
A common class-attribute trap
s1.count = 100This creates or changes an instance attribute named count for s1; it does not reliably update the shared class attribute.
To update the shared value clearly, use:
Student.count = 100Encapsulation
Encapsulation combines attributes and the methods that operate on them inside one class.
Caption: Encapsulation keeps state and related methods inside the object; outside code should use the public methods rather than changing _balance directly.
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self._balance = balance
def deposit(self, amount):
if amount > 0:
self._balance = self._balance + amount
return True
return False
def withdraw(self, amount):
if 0 < amount <= self._balance:
self._balance = self._balance - amount
return True
return False
def get_balance(self):
return self._balanceThe class stores account data and provides controlled operations on that data.
The withdraw() method can enforce the rule that the balance must not become negative. If outside code changes the balance directly, that rule can be bypassed.
Public Methods and Internal Attributes
A public method is intended to be used by code outside the class.
Examples:
account.deposit(50)
account.withdraw(20)
account.get_balance()An underscore prefix such as _balance signals that an attribute is intended for internal use:
self._balance = balanceIn Python, a single underscore is a convention rather than an absolute access barrier. Outside code can still access the attribute, but doing so ignores the intended interface.
For syllabus explanations, it is useful to distinguish:
- design intention: internal data should be accessed or changed through public methods;
- Python behaviour: an underscore does not make access impossible.
Getters and Setters
A getter returns an attribute value.
def get_balance(self):
return self._balanceA setter changes an attribute value, usually after checking it.
def set_mark(self, mark):
if 0 <= mark <= 100:
self._mark = mark
return True
return FalseNot every attribute needs both a getter and a setter. A setter is useful only when outside code should be allowed to change the value.
A controlled method may also be more meaningful than a general setter. For a bank account, deposit() and withdraw() express the allowed operations more clearly than set_balance().
Information Hiding
Information hiding means that outside code uses the public operations of a class without depending directly on its internal representation.
Preferred:
account.deposit(50)
account.withdraw(20)
balance = account.get_balance()Avoid:
account._balance = -999The second statement bypasses the class rules and may leave the object in an invalid state.
Information hiding supports:
- data integrity;
- controlled validation;
- reduced dependence on implementation details;
- easier modification of the class.
Implementation Independence
Caption: Outside code uses a stable public interface, so the class can replace one internal implementation with another without changing the calling code.
Suppose BankAccount initially stores one number:
self._balance = balanceLater, the class may instead store a list of transactions and calculate the balance from that list.
If the public methods remain:
deposit()
withdraw()
get_balance()the code using the class may not need to change.
This is implementation independence: users of the class depend on what its public methods do, not on how the class performs the work internally.
Paper 1 and Paper 2 Emphasis
Paper 1-style explanation
Be ready to define and distinguish:
- class and object;
- attribute and method;
- class attribute and instance attribute;
- encapsulation and information hiding;
- information hiding and implementation independence.
A strong answer states both the mechanism and the benefit. For example:
Encapsulation combines data and methods in a class. Access through public methods allows the class to validate changes and hides the internal representation from outside code.
Paper 2-style coding
Be ready to:
- define a class;
- initialise instance attributes using
self; - instantiate multiple objects;
- write methods that read or update state;
- implement simple getters, setters, or validation methods;
- use a class attribute for genuinely shared state;
- trace object state after a sequence of method calls.
Common Mistakes
- Saying that a class and an object are the same thing.
- Forgetting
selfin an instance method definition. - Assigning to a local variable instead of an instance attribute.
- Passing
selfexplicitly in an ordinary method call. - Treating a class attribute as though every object has a separate copy.
- Updating a shared class attribute through an instance and accidentally creating an instance attribute.
- Claiming that a single underscore makes a Python attribute completely inaccessible.
- Giving every attribute a setter even when the value should not be changed.
- Describing information hiding without explaining the public method interface.
- Mixing methods into the attribute section of a class diagram.
Check Your Understanding
- What is the difference between a class and an object?
- What does
selfrefer to? - Why does
self.name = namecreate lasting object state butname = namedoes not? - What is the difference between an instance attribute and a class attribute?
- Why should outside code call
withdraw()instead of setting_balancedirectly? - How does information hiding support implementation independence?
Answers:
- A class defines a type of object; an object is a particular instance created from the class.
- The current object using the method.
self.namestores the value as an attribute of the object, whereasnamealone is local to the method.- An instance attribute belongs to one object; a class attribute is associated with the class and used as shared state.
withdraw()can enforce rules and preserve a valid balance.- Outside code depends on public methods, so the internal storage can change without necessarily changing the calling code.