codejump

AcademyPython

OOP and Dunder Methods in Python

How __init__, __repr__ and __eq__ hook a class into built-in operators, why super() matters in a diamond hierarchy, and when composition beats inheritance.

3 min readUpdated

3 exercises

Waiting at the end of this page.

Dunder methods are how Python's own syntax reaches your class

len(x), x == y, print(x), x + y are not special-cased for built-in types only — each one calls a method with a double-underscore name on x, and defining that method on a custom class makes the built-in syntax work on it too:

python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __repr__(self):
        return f'Point({self.x}, {self.y})'

    def __eq__(self, other):
        return isinstance(other, Point) and self.x == other.x and self.y == other.y

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

Point(1, 2) + Point(3, 4) now works because + calls __add__. Point(1, 2) == Point(1, 2) returns True because == calls __eq__ — without it, two Points with identical coordinates would compare unequal, since the default __eq__ inherited from object compares identity, not value.

repr versus str

__repr__ is what a debugger, a REPL, and a list's own repr() show — unambiguous, ideally valid Python that could recreate the object. __str__ is what print() and str() use for a reader-facing description, and it falls back to __repr__ automatically if not defined. Defining only __repr__ and skipping __str__ is the normal, idiomatic choice for most classes — that fallback exists precisely so one method covers both cases unless the two genuinely need to differ.

__init__ is not the constructor

__new__ creates the instance; __init__ only initialises one that already exists. __init__ returning anything other than None is a TypeError, because by the time it runs the object already exists — its job is to set attributes, not produce a value. Overriding __new__ is rare and mostly reserved for immutable types or metaclass work; almost every class only ever needs __init__.

super() and the method resolution order

python
class Base:
    def __init__(self):
        print('Base')

class Left(Base):
    def __init__(self):
        super().__init__()
        print('Left')

class Right(Base):
    def __init__(self):
        super().__init__()
        print('Right')

class Diamond(Left, Right):
    def __init__(self):
        super().__init__()
        print('Diamond')

Diamond()
# Base
# Right
# Left
# Diamond

Base prints once, not twice, even though both Left and Right inherit from it. super() does not mean "my direct parent" — it means "the next class in the method resolution order," a single linearised sequence Python computes once per class (Diamond.__mro__) that visits every ancestor exactly once. That is what keeps a diamond-shaped hierarchy from running a shared base class's __init__ twice, which would happen if super() were a literal reference to the immediate parent instead.

Where composition reads better

python
class Engine:
    def start(self):
        ...

class Car:
    def __init__(self):
        self.engine = Engine()

    def start(self):
        self.engine.start()

Car inheriting from Engine would mean every car is an engine, which is not true and invites calling engine-only methods on a car by mistake. Car holding an Engine means a car has one — the relationship inheritance actually models is "is a more specific version of," and reaching for it to mean "is built from" is the most common overuse of the tool in real codebases.

dataclasses generate the boilerplate

python
from dataclasses import dataclass

@dataclass
class Point:
    x: int
    y: int

This one decorator generates __init__, __repr__ and __eq__ from the type-annotated fields alone — the exact three methods written out by hand above. Reaching for @dataclass on a class whose whole job is holding a fixed set of named fields is the modern default; writing all three dunder methods manually is now mostly reserved for classes that need behaviour a dataclass cannot express.

Now practice it

1 challenge · 2 quizzes

Reading this page is the cheap half. These are the exercises that make you use it.