Topic 08 / 15
Object-Oriented Python — Classes
Defining a Class
class Course:
platform = "Coding India" # class attribute — shared by all
def __init__(self, title, price): # constructor
self.title = title # instance attributes — per object
self.price = price
self.students = 0
def enroll(self): # method — self = this instance
self.students += 1
def revenue(self):
return self.students * self.price
django = Course("Django Mastery", 199)
django.enroll()
django.enroll()
print(django.revenue()) # 398
print(django.platform) # Coding Indiaself is the instance the method was called on — Python passes it automatically. Forgetting it in the method signature is the classic first OOP error.
Inheritance
class VideoCourse(Course):
def __init__(self, title, price, hours):
super().__init__(title, price) # run the parent constructor
self.hours = hours
def revenue(self): # override
base = super().revenue()
return base * 1.1 # video courses earn a bonusisinstance(obj, Course) is True for both classes. Prefer shallow hierarchies — one or two levels. Beyond that, composition (“has-a”) usually beats inheritance (“is-a”).
Dunder Methods — Make Objects Pythonic
Double-underscore methods hook your objects into Python’s syntax:
class Course:
# ...
def __repr__(self): # shown in debugger / REPL
return f"Course({self.title!r}, {self.price})"
def __eq__(self, other): # makes == meaningful
return self.title == other.title
def __len__(self): # len(course)
return self.studentsOthers worth knowing: __str__ (print), __lt__ (sorting), __iter__ (for loops), __enter__/__exit__ (with blocks).
Properties — Computed Attributes
class Course:
# ...
@property
def is_free(self):
return self.price == 0
course.is_free # no parentheses — reads like an attributeClass Methods & Static Methods
class Course:
@classmethod
def from_dict(cls, data): # alternative constructor
return cls(data["title"], data["price"])
@staticmethod
def format_price(amount): # utility, no self needed
return f"₹{amount:,.0f}"Dataclasses — Skip the Boilerplate
For classes that mostly hold data, @dataclass writes __init__, __repr__ and __eq__ for you:
from dataclasses import dataclass, field
@dataclass
class Student:
name: str
score: int = 0
tags: list = field(default_factory=list)
s = Student("Ravi", 82) # __init__ generated from the fields
print(s) # Student(name='Ravi', score=82, tags=[])Modern Python uses dataclasses heavily — reach for one before writing a manual __init__.