MOD-18 · Sequence #18 · Object-Oriented Programming

Python Classes & Objects (OOP)

Classes provide blueprints for creating user-defined objects. CPython models objects using state dictionaries (__dict__), explicit instance receivers (self), and dunder methods (__init__, __str__, __repr__, __eq__) to enable idiomatic Pythonic behavior.

Live Interactive Code Snippet (Python 3.12 CPython WASM) Open in Practice Mode →
class Student:
    def __init__(self, name, roll):
        self.name = name
        self.roll = roll

s = Student("Alice", 101)
print(s.name, s.roll)

Core Principles & Mental Models

1. Class Definition & The Explicit `self` Receiver

Why Python Requires `self` in Method Signatures

  • In Python, `self` represents the instance calling the method. Unlike C++ or Java where `this` is an implicit keyword, Python makes the instance receiver explicit.
  • When you call `obj.method(arg)`, Python translates this under the hood into `ClassName.method(obj, arg)`.
  • The `__init__` method is the object initializer (constructor), called immediately after the object is allocated by `__new__`.
class Student:
    def __init__(self, name: str, gpa: float):
        self.name = name  # Instance attribute
        self.gpa = gpa

    def is_honors(self) -> bool:
        return self.gpa >= 3.8

s = Student("Ada Lovelace", 3.95)
print(f"{s.name} honors: {s.is_honors()}")
💡 Key Takeaway: The first parameter of any instance method must be the instance receiver, conventionally named self.

2. Special Dunder Methods (__repr__ vs. __str__)

Representation for Developers vs. End Users

  • `__str__` is called by `str(obj)` and `print(obj)` to return a readable, user-friendly string.
  • `__repr__` is called by `repr(obj)` and interactive REPLs, aimed at developers. Ideally, `repr(obj)` should return valid Python code to recreate the object.
class Coordinate:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f"Coordinate({self.x}, {self.y})"

    def __str__(self):
        return f"({self.x}, {self.y})"

pt = Coordinate(4, 9)
print("str:", str(pt))
print("repr:", repr(pt))
💡 Key Takeaway: Always implement __repr__ so your custom objects display cleanly in debugger outputs and collections.

Interactive Walkthrough Examples

Custom Vector Class with Operator Overloading (Intermediate)

Implementing `__add__` and `__eq__` for a 2D math Vector class.

class Vector2D:
    def __init__(self, x: float, y: float):
        self.x = x
        self.y = y

    def __add__(self, other: 'Vector2D') -> 'Vector2D':
        return Vector2D(self.x + other.x, self.y + other.y)

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, Vector2D):
            return False
        return self.x == other.x and self.y == other.y

    def __repr__(self):
        return f"Vector2D({self.x}, {self.y})"

v1 = Vector2D(2, 5)
v2 = Vector2D(3, 1)
v3 = v1 + v2
print("v1 + v2 =", v3)
print("v3 == Vector2D(5, 6)?", v3 == Vector2D(5, 6))

Explanation: Operator overloading enables natural mathematical syntax with custom objects.

Common Misconceptions & Exam Traps

Common Pitfall: Class Variables vs. Instance Variables Mutability Trap

Variables declared in the class body are class attributes shared by all instances.

Buggy Code
class Student:
    courses = []  # CLASS variable shared across ALL instances!
    def __init__(self, name):
        self.name = name

s1 = Student("Alice")
s2 = Student("Bob")
s1.courses.append("CS101")
print("Bob courses:", s2.courses)  # ['CS101'] - Corrupted state!
Correct Solution
class Student:
    def __init__(self, name):
        self.name = name
        self.courses = []  # INSTANCE variable unique to each student

s1 = Student("Alice")
s2 = Student("Bob")
s1.courses.append("CS101")
print("Bob courses:", s2.courses)  # []

Exam Trap Note: Exams test whether modifying `s1.courses` alters `s2.courses`. If defined at the class level, yes it does!

Conceptual Knowledge Self-Check Quiz

Q1: What is the purpose of the `self` parameter in a Python class method?

  1. It points to the parent superclass
  2. It references the current instance of the class
  3. It is a keyword that allocates new memory
  4. It imports the class namespace

`self` is the explicit reference to the current object instance, allowing methods to read and modify instance state.

Algorithmic Practice Challenges

Idiomatic Best Practices

  • ✅ Initialize all instance attributes inside `__init__`.
  • ✅ Use `@classmethod` for factory constructors and `@staticmethod` for utility helpers that do not access instance state.

All 26 Curriculum Modules