
Chapter Outline
Chapter 8: Object-Oriented Programming (OOP)
As applications grow, you’ll need ways to organize code around data and behavior. That’s where Object-Oriented Programming (OOP) comes in.
In this chapter, we’ll cover:
- Classes and Objects
- Attributes and Methods
- Inheritance
- Dunder (Magic) Methods like
__str__and__repr__ - A practical project: Task Manager class
- Writing unit tests for class behaviors
8.1 Understanding Classes and Objects
What is a Class?
A class is a blueprint for creating objects that encapsulate data (attributes) and behavior (methods).
Simple Class Example
pythonclass Dog:def __init__(self, name, breed):self.name = nameself.breed = breeddef bark(self):return f"{self.name} says Woof!"
This is the constructor method, called automatically when you create a new Dog object.
__init__ is a special method in Python (known as a dunder or "double underscore" method).
It takes three arguments:
self: A reference to the current object being created.name: The name of the dog (e.g., "Buddy").breed: The breed of the dog (e.g., "Labrador").
Creating an Object
pythondog1 = Dog("Rex", "German Shepherd")print(dog1.bark()) # Output: Rex says Woof!
8.2 Attributes and Methods
- Attributes = Variables associated with the object (
self.name,self.breed) - Methods = Functions defined inside a class that operate on its data (
bark())
8.3 Inheritance
Inheritance allows you to extend existing classes.
Example:
pythonclass Animal:def speak(self):return "Some sound"class Cat(Animal):def speak(self):return "Meow"cat = Cat()print(cat.speak()) # Output: Meow
Here Cat is the child class, and Animal is the parent class.
8.4 Dunder Methods (__str__, __repr__)
Dunder methods (also called magic methods) are special methods that Python uses to enable the behavior of built-in operations such as:
- Object creation
- Arithmetic
- Comparisons
- String conversion
- Iteration
They are always surrounded by double underscores, e.g., __init__, __str__, __len__.
These methods allow you to define how custom objects behave like built-in types.
Commonly Used Dunder Methods (With Examples)
| Dunder Method | Purpose | Example |
|---|---|---|
__init__ | Constructor: initialize an object | __init__(self) |
__str__ | String representation (str(obj)) | __str__(self) |
__repr__ | Developer representation (repr(obj)) | __repr__(self) |
__len__ | Used by len(obj) | __len__(self) |
__getitem__ | Indexing: obj[index] | __getitem__(self, index) |
__setitem__ | Set value at index: obj[index] = x | __setitem__(self, index, value) |
__eq__, __lt__, etc. | Comparisons: ==, <, etc. | __eq__(self, other) |
__add__, __mul__, etc. | Arithmetic operations | __add__(self, other) |
__call__ | Make object callable like a function | __call__(self, *args) |
__enter__, __exit__ | Context managers (with block) | __enter__(self), __exit__(self, ...) |
Example:
python1class Book:2 def __init__(self, title, author):3 self.title = title4 self.author = author56 def __str__(self):7 return f"'{self.title}' by {self.author}"89 def __eq__(self, other):10 return self.title == other.title and self.author == other.author1112 def __repr__(self):13 return f"Book(title='{self.title}', author='{self.author}')"1415book = Book("1984", "George Orwell")16print(book) # Output: '1984' by George Orwell17print(repr(book)) # Output: Book(title='1984', author='George Orwell')18book2 = Book("1984", "George Orwell")19print(book == book2) # Output: True
8.5 Practical Project: Task Manager Class
Let’s build a simple Task Manager where you can:
- Add tasks
- Mark tasks as done
- List all tasks
- Search tasks by status
task_manager.py1class Task:2 def __init__(self, title, description=""):3 self.title = title4 self.description = description5 self.completed = False67 def mark_done(self):8 self.completed = True910 def __str__(self):11 status = "Done" if self.completed else "Pending"12 return f"[{status}] {self.title}{ ": " + self.description if len(self.description) > 0 else "" }"1314class TaskManager:15 def __init__(self):16 self.tasks = []1718 def add_task(self, title, description=""):19 task = Task(title, description)20 self.tasks.append(task)21 return task2223 def list_tasks(self):24 return [str(task) for task in self.tasks]2526 def get_pending_tasks(self):27 return [str(task) for task in self.tasks if not task.completed]2829 def get_completed_tasks(self):30 return [str(task) for task in self.tasks if task.completed]
Usage Example
python1if __name__ == "__main__":2 manager = TaskManager()34 manager.add_task("Write unit tests", "For the task manager app")5 manager.add_task("Refactor code")67 manager.tasks[0].mark_done()89 print("\nAll Tasks:")10 print("\n".join(manager.list_tasks()))1112 print("\nPending Tasks:")13 print(manager.get_pending_tasks())
Expected Output:
bashAll Tasks:[Done] Write unit tests: For the task manager application[Pending] Refactor codePending Tasks:[<__main__.Task object at 0x...>]
You can improve the pending tasks output by adding a
__repr__method to theTaskclass if needed.
8.6 Writing Unit Tests for the Task Manager
test_task_manager.py1import pytest2from task_manager import TaskManager34@pytest.fixture5def manager():6 return TaskManager()78def test_add_task(manager):9 task = manager.add_task("Test Task", "Description")10 assert len(manager.tasks) == 111 assert task.title == "Test Task"12 assert task.description == "Description"13 assert task.completed is False1415def test_mark_task_done(manager):16 task = manager.add_task("Complete me")17 task.mark_done()18 assert task.completed is True1920def test_list_tasks(manager):21 manager.add_task("Task 1")22 manager.add_task("Task 2")23 task_list = manager.list_tasks()24 assert "[Pending] Task 1" in task_list[0]25 assert "[Pending] Task 2" in task_list[1]2627def test_filter_tasks(manager):28 t1 = manager.add_task("Task 1")29 t2 = manager.add_task("Task 2")30 t1.mark_done()3132 pending = manager.get_pending_tasks()33 completed = manager.get_completed_tasks()3435 assert t2 in pending36 assert t1 in completed
Run Your Tests
bashpytest
Expected result:
bashcollected 4 itemstest_task_manager.py .... [100%]4 passed in 0.03s
8.7 Why This Matters for Web and API Development
OOP is essential when you start building:
- Models in Django
- Service layers in FastAPI
- Domain logic in larger apps
- Serializable objects for APIs
The Task Manager class you just built could easily become part of a REST API backend or a database model.
Conclusion
You’ve learned:
- How to create classes and objects
- How to work with attributes, methods, and inheritance
- How to add dunder methods for better object representation
- How to write and test OOP-style Python code
What is Next
In Chapter 6: Advanced Object-Oriented Programming in Python, we’ll build:
- Inheritance and Polymorphism
- Duck Typing and Dynamic Behavior
- Abstract Base Classes (ABCs)
- Composition vs. Inheritance
Check your understanding
Test your knowledge of Object-Oriented Programming (OOP) in Python