OOP in Python
Python OOP uses class and __init__. self is the explicit object reference. Special methods (__str__, __len__, __eq__) make your objects behave like built-in types.
class Learner:
"""A learner enrolled in the Your IT Tutor programme."""
# Class attribute — shared by all instances:
institution = "Your IT Tutor"
def __init__(self, name: str, age: int, course: str, mark: float = 0.0):
# Instance attributes — unique to each object:
self.name = name
self.age = age
self.course = course
self.mark = mark
def introduce(self):
"""Print a self-introduction."""
print(f"I am {self.name}, aged {self.age}, "
f"studying {self.course} at {self.institution}.")
def grade_label(self) -> str:
if self.mark >= 80: return "Distinction"
if self.mark >= 60: return "Merit"
if self.mark >= 50: return "Pass"
return "Not yet competent"
# Creating objects:
l1 = Learner("Thandi", 21, "Python", 74.5)
l2 = Learner("Sipho", 24, "Java", 81.0)
l1.introduce()
l2.introduce()
print(l1.grade_label())
Special (Dunder) Methods
Dunder methods let your objects integrate with Python's built-in operations — printing, comparison, length, iteration.
class Learner:
def __init__(self, name, mark):
self.name = name
self.mark = mark
def __str__(self):
"""Called by print() and str()"""
return f"Learner({self.name}, {self.mark}%)"
def __repr__(self):
"""Called in the REPL and for debugging"""
return f"Learner(name={self.name!r}, mark={self.mark!r})"
def __eq__(self, other):
"""Called by =="""
if not isinstance(other, Learner):
return NotImplemented
return self.name == other.name and self.mark == other.mark
def __lt__(self, other):
"""Called by < — enables sorting"""
return self.mark < other.mark
def __len__(self):
"""Called by len() — returns meaningful length"""
return len(self.name)
l1 = Learner("Thandi", 74.5)
l2 = Learner("Sipho", 81.0)
l3 = Learner("Thandi", 74.5)
print(l1) # Learner(Thandi, 74.5%) — __str__
print(l1 == l3) # True — __eq__
print(l1 < l2) # True — __lt__
print(sorted([l2,l1])) # sorted by mark — __lt__
print(len(l1)) # 6 — __len__
Practice Task
Your Turn
Build a BankAccount class with: account_number (auto-generated, read-only), holder_name, balance (float). Implement __str__, __repr__, __eq__ (by account_number), __iadd__ (deposit with +=), __isub__ (withdraw with -=, raise InsufficientFundsError if insufficient). Test all operations.
Common Mistakes
- Forgetting self in the method definition —
def introduce():instead ofdef introduce(self):raises TypeError on call. - Accessing self.name inside a method as just name — NameError.
- Mutable default arguments in __init__ — use None.
- Confusing class attributes and instance attributes — class attrs are shared, instance attrs are per-object.
Professional Tip
Implement __repr__ to produce a string that could recreate the object: Learner(name='Thandi', mark=74.5). This is invaluable for debugging — it is what Python shows in the REPL and in error messages.
Mini Quiz
What is the purpose of self in Python instance methods?