Classes and Objects — Building Modular, Reusable Code
Object-Oriented Programming (OOP) is a programming paradigm that models software around **objects** — real-world entities that have **attributes** (data) and **methods** (behaviors).
Chapter 5: Object-Oriented Programming (OOP)
Sub-chapter: Classes and Objects — Building Modular, Reusable Code
Object-Oriented Programming (OOP) is a programming paradigm that models software around objects — real-world entities that have attributes (data) and methods (behaviors).
In Python, everything is an object — classes provide the blueprints, and objects are the instances built from them.
🧩 Why OOP?
| Approach | Description | Example Use |
|---|---|---|
| Procedural | Code organized as functions and data structures | Small scripts, utilities |
| Object-Oriented | Code organized around objects and classes | Games, APIs, enterprise systems |
OOP improves modularity, reusability, and scalability — essential for modern software development.
🧱 Defining a Class
A class is a template for creating objects.
It defines attributes (variables) and methods (functions) that describe object behavior.
class Person:
def __init__(self, name, age):
self.name = name # Instance attribute
self.age = age
def greet(self):
return f"Hello, my name is {self.name} and I am {self.age} years old."
Creating and Using Objects
alice = Person("Alice", 30)
bob = Person("Bob", 25)
print(alice.greet())
print(bob.name)
Each object (
alice,bob) has its own state (data) and can perform actions (methods).
🧠 The self Keyword
selfrefers to the current instance of the class.- It’s automatically passed to instance methods.
- You must include it explicitly in method definitions.
class Dog:
def __init__(self, name):
self.name = name
def bark(self):
print(f"{self.name} says woof!")
🏗️ Class vs Instance Attributes
- Instance attributes belong to specific objects.
- Class attributes are shared among all instances.
class Circle:
pi = 3.14159 # Class attribute (shared)
def __init__(self, radius):
self.radius = radius # Instance attribute (unique)
def area(self):
return Circle.pi * (self.radius ** 2)
🧰 Constructors and __init__
The __init__ method is called automatically when an object is created — used to initialize data.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
def __str__(self):
return f"'{self.title}' by {self.author}"
💬 String Representations: __str__ and __repr__
| Method | Purpose | Example Output |
|---|---|---|
__str__() | User-friendly string for printing | "Book('1984', 'Orwell')" |
__repr__() | Developer-focused representation | "Book(title='1984', author='Orwell')" |
class Example:
def __init__(self, value):
self.value = value
def __str__(self):
return f"Example with value {self.value}"
def __repr__(self):
return f"Example(value={self.value!r})"
🧩 Class Methods and Static Methods
@classmethod
Operates on the class itself, not individual instances.
class Car:
cars_built = 0
def __init__(self, model):
self.model = model
Car.cars_built += 1
@classmethod
def total_built(cls):
return f"Total cars built: {cls.cars_built}"
@staticmethod
Does not depend on class or instance state.
class MathUtils:
@staticmethod
def add(a, b):
return a + b
print(MathUtils.add(10, 5))
🧱 Encapsulation and Data Protection
Python uses naming conventions for attribute visibility:
| Convention | Example | Meaning |
|---|---|---|
| Public | self.name | Accessible everywhere |
| Protected | self._age | Internal use (by convention) |
| Private | self.__salary | Name mangling prevents external access |
class Employee:
def __init__(self, name, salary):
self.name = name
self.__salary = salary # Private attribute
def show_info(self):
return f"{self.name} earns ${self.__salary}"
🧩 Real-World Example — Library System
Let’s model a simple Library Management System using OOP principles.
class Book:
def __init__(self, title, author):
self.title = title
self.author = author
self.is_borrowed = False
def borrow(self):
if not self.is_borrowed:
self.is_borrowed = True
return f"You have borrowed '{self.title}'."
return f"'{self.title}' is already borrowed."
def return_book(self):
self.is_borrowed = False
return f"'{self.title}' has been returned."
class Member:
def __init__(self, name):
self.name = name
self.borrowed_books = []
def borrow_book(self, book):
result = book.borrow()
if "borrowed" in result:
self.borrowed_books.append(book.title)
return f"{self.name}: {result}"
def show_books(self):
return f"{self.name}'s borrowed books: {self.borrowed_books}"
class Library:
def __init__(self, name):
self.name = name
self.books = []
def add_book(self, book):
self.books.append(book)
def list_books(self):
return [f"{b.title} by {b.author}" for b in self.books]
Example Usage
lib = Library("Central Library")
book1 = Book("1984", "George Orwell")
book2 = Book("Dune", "Frank Herbert")
lib.add_book(book1)
lib.add_book(book2)
member = Member("Alice")
print(member.borrow_book(book1))
print(member.show_books())
print(book1.return_book())
📊 UML-Style Class Diagram (Text Representation)
+----------------+ +----------------+ +----------------+
| Library |<>---->| Book | | Member |
+----------------+ +----------------+ +----------------+
| - name | | - title | | - name |
| - books | | - author | | - borrowed_books|
+----------------+ | - is_borrowed | +----------------+
| + add_book() | +----------------+ | + borrow_book() |
| + list_books() | | + borrow() | | + show_books() |
+----------------+ | + return_book()| +----------------+
🧠
LibraryaggregatesBookobjects and interacts withMemberinstances to perform operations.
⚙️ Best Practices for Classes and Objects
✅ Keep each class focused on a single responsibility.
✅ Use meaningful names for attributes and methods.
✅ Use private attributes for sensitive data.
✅ Prefer composition over deep inheritance.
✅ Implement __str__ and __repr__ for better debugging.
✅ Keep classes small and modular — large classes are hard to maintain.
🧾 Summary
- Classes define the structure and behavior of objects.
- Objects represent real-world entities with attributes and methods.
- The
selfkeyword links instance methods to specific objects. - Use class methods, static methods, and encapsulation for clarity and safety.
- Real-world systems (like libraries, stores, or games) can be modeled naturally with OOP.
By mastering classes and objects, you gain the ability to design structured, modular, and scalable applications — the foundation for advanced OOP topics such as inheritance, polymorphism, and composition.