Python 3 — OOP: Classes & Objects

🐍 Python 3 🟢 Lesson 14 📅 July 2026

Object-Oriented Programming (OOP) is a programming paradigm that organizes code around objects — data structures that bundle related data and behavior together. Python is a fully object-oriented language. Classes are the blueprints; objects are the real things built from those blueprints.

1 What is a Class?

A class is a blueprint for creating objects. Think of it like a cookie cutter — the cutter is the class, and the cookies are the objects (instances):

Python 3 — First Class▶ Run Code
# Define a class
class Dog:
    # Class variable — shared by ALL instances
    species = "Canis familiaris"

    # Constructor — called when creating an object
    def __init__(self, name, breed, age):
        # Instance variables — unique to each object
        self.name = name
        self.breed = breed
        self.age = age

    # Instance method
    def bark(self):
        return f"{self.name} says: Woof! Woof!"

    def describe(self):
        return f"{self.name} is a {self.age}-year-old {self.breed}."

# Create objects (instances)
dog1 = Dog("Buddy", "Golden Retriever", 3)
dog2 = Dog("Max", "German Shepherd", 5)

print(dog1.name)          # Buddy
print(dog2.breed)         # German Shepherd
print(dog1.bark())        # Buddy says: Woof! Woof!
print(dog2.describe())    # Max is a 5-year-old German Shepherd.
print(Dog.species)        # Canis familiaris (class variable)
2 The __init__ Constructor
Python 3 — __init__▶ Run Code
class BankAccount:
    def __init__(self, owner, initial_balance=0):
        self.owner = owner
        self.balance = initial_balance
        self.transactions = []   # Mutable default must be in __init__!

    def deposit(self, amount):
        if amount <= 0:
            raise ValueError("Deposit must be positive!")
        self.balance += amount
        self.transactions.append(f"Deposit: +₹{amount}")
        print(f"✅ Deposited ₹{amount}. Balance: ₹{self.balance}")

    def withdraw(self, amount):
        if amount > self.balance:
            raise ValueError("Insufficient funds!")
        self.balance -= amount
        self.transactions.append(f"Withdrawal: -₹{amount}")
        print(f"✅ Withdrew ₹{amount}. Balance: ₹{self.balance}")

    def statement(self):
        print(f"\n=== Statement for {self.owner} ===")
        for t in self.transactions:
            print(f"  {t}")
        print(f"  Current Balance: ₹{self.balance}")

acc = BankAccount("Balaji", 10000)
acc.deposit(5000)
acc.withdraw(3000)
acc.statement()
3 Instance vs Class Variables
Python 3 — Variable Types▶ Run Code
class Employee:
    # Class variable — shared by ALL employees
    company = "TechCorp"
    employee_count = 0

    def __init__(self, name, role, salary):
        # Instance variables — unique to each employee
        self.name = name
        self.role = role
        self.salary = salary
        Employee.employee_count += 1  # Update class variable

    def get_info(self):
        return f"{self.name} ({self.role}) at {Employee.company}"

emp1 = Employee("Alice", "Developer", 80000)
emp2 = Employee("Bob", "Designer", 70000)

print(emp1.get_info())         # Alice (Developer) at TechCorp
print(emp2.get_info())         # Bob (Designer) at TechCorp
print(f"Total employees: {Employee.employee_count}")  # 2

# Class variable affects ALL instances
Employee.company = "MegaCorp"
print(emp1.get_info())   # Alice (Developer) at MegaCorp
print(emp2.get_info())   # Bob (Designer) at MegaCorp
4 Properties & Encapsulation
Python 3 — Properties▶ Run Code
class Circle:
    def __init__(self, radius):
        self._radius = radius   # _ prefix = "private by convention"

    @property
    def radius(self):
        """Getter — access like an attribute"""
        return self._radius

    @radius.setter
    def radius(self, value):
        """Setter — validates before setting"""
        if value < 0:
            raise ValueError("Radius cannot be negative!")
        self._radius = value

    @property
    def diameter(self):
        return self._radius * 2

    @property
    def area(self):
        import math
        return math.pi * self._radius ** 2

    @property
    def circumference(self):
        import math
        return 2 * math.pi * self._radius

c = Circle(5)
print(f"Radius: {c.radius}")         # 5
print(f"Diameter: {c.diameter}")     # 10
print(f"Area: {c.area:.2f}")         # 78.54
c.radius = 10                         # Uses setter
print(f"New radius: {c.radius}")
5 Static & Class Methods
Python 3 — Static & Class Methods▶ Run Code
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    @property
    def fahrenheit(self):
        return (self.celsius * 9/5) + 32

    @classmethod
    def from_fahrenheit(cls, f):
        """Alternative constructor from Fahrenheit."""
        return cls((f - 32) * 5/9)

    @staticmethod
    def is_freezing(celsius):
        """Utility — doesn't need class or instance."""
        return celsius <= 0

    def __str__(self):
        return f"{self.celsius}°C / {self.fahrenheit}°F"

# Instance method
t1 = Temperature(100)
print(t1)                          # 100°C / 212.0°F

# Class method — alternative constructor
t2 = Temperature.from_fahrenheit(98.6)
print(t2)                          # 37.0°C / 98.6°F

# Static method — no instance needed
print(Temperature.is_freezing(0))   # True
print(Temperature.is_freezing(25))  # False
6 __str__ and __repr__
Python 3 — String Representation▶ Run Code
class Product:
    def __init__(self, name, price, quantity):
        self.name = name
        self.price = price
        self.quantity = quantity

    def __str__(self):
        """Human-readable string — used by print()"""
        return f"{self.name}: ₹{self.price} (Stock: {self.quantity})"

    def __repr__(self):
        """Developer-friendly — used in REPL, lists"""
        return f"Product(name={self.name!r}, price={self.price}, qty={self.quantity})"

    def total_value(self):
        return self.price * self.quantity

p = Product("Laptop", 59999, 10)
print(p)           # Product.__str__: Laptop: ₹59999 (Stock: 10)
print(repr(p))     # Product.__repr__: Product(name='Laptop', ...)

products = [p, Product("Mouse", 999, 50)]
print(products)    # Uses __repr__ for list display
7 Dataclasses (Python 3.7+)
Python 3.7+ — dataclass▶ Run Code
from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    age: int
    score: float = 0.0
    subjects: list = field(default_factory=list)

    def grade(self):
        if self.score >= 90: return "A"
        if self.score >= 80: return "B"
        if self.score >= 70: return "C"
        return "F"

    def __post_init__(self):
        """Runs after __init__ — for validation"""
        if self.age < 5 or self.age > 100:
            raise ValueError(f"Invalid age: {self.age}")

s1 = Student("Alice", 20, 92.5, ["Math", "Python"])
s2 = Student("Bob", 22, 78.0)

print(s1)           # Student(name='Alice', age=20, ...)
print(s1.grade())   # A
print(s2.grade())   # B
8 Coding Challenge

Build a Library Management System using classes:

  • Book class: title, author, isbn, available (default True)
  • Library class with a list of books and these methods:
    • add_book(book) — add a Book
    • borrow_book(isbn) — sets available=False (raises ValueError if not available)
    • return_book(isbn) — sets available=True
    • search(query) — finds books by title or author
    • available_books() — lists only available books
  • Add __str__ to both classes for nice display