Topic 08 / 15

Object-Oriented Python — Classes

~12 min read  //  Python Series  //  Coding India

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 India

self 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 bonus

isinstance(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.students

Others 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 attribute

Class 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__.