Python 3 — OOP: Classes & Objects
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:
Bookclass: title, author, isbn, available (default True)Libraryclass with a list of books and these methods:add_book(book)— add a Bookborrow_book(isbn)— sets available=False (raises ValueError if not available)return_book(isbn)— sets available=Truesearch(query)— finds books by title or authoravailable_books()— lists only available books
- Add
__str__to both classes for nice display