Dunder Methods

The Course
January 10, 2026
5 min read

Python’s built-in functions and operators are not special cases — they are calls to methods with double-underscore names. len(x) calls x.__len__(). a + b calls a.__add__(b). Implement those methods and your own objects work with the whole language.

They are called dunder methods, short for double underscore.

__str__ and __repr__

Without them, printing an object is useless:

The default is unhelpful Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

p = Point(3, 4)
print(p)
print([p, p])

Add the two string methods and both problems disappear:

Readable and debuggable Python
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        """For humans - what print() shows."""
        return f"({self.x}, {self.y})"

    def __repr__(self):
        """For programmers - ideally, code that would recreate the object."""
        return f"Point(x={self.x}, y={self.y})"

p = Point(3, 4)
print(p)          # uses __str__
print(repr(p))    # uses __repr__
print([p, p])     # containers always use __repr__
If you only write one, write __repr__

__str__ falls back to __repr__ when it is missing, so a single good __repr__ fixes printing, debugging and list display all at once.

Comparison

Equality and ordering Python
class Money:
    def __init__(self, cents):
        self.cents = cents

    def __repr__(self):
        return f"Money({self.cents})"

    def __eq__(self, other):
        return self.cents == other.cents

    def __lt__(self, other):
        return self.cents < other.cents

a = Money(500)
b = Money(500)
c = Money(120)

print(a == b)             # without __eq__ this would be False - different objects
print(a < c, c < a)
print(sorted([a, c, b]))  # sorting only needs __lt__
print(max([a, c]))

Defining __eq__ and __lt__ is enough for sorting, min and max. The functools.total_ordering decorator fills in >, <= and >= for you.

Length, membership, indexing

Behaving like a collection Python
class Playlist:
    def __init__(self, name):
        self.name = name
        self.tracks = []

    def add(self, track):
        self.tracks.append(track)
        return self

    def __len__(self):
        return len(self.tracks)

    def __contains__(self, track):
        return track in self.tracks

    def __getitem__(self, index):
        return self.tracks[index]

    def __iter__(self):
        return iter(self.tracks)

playlist = Playlist("Focus")
playlist.add("Kind of Blue").add("Blue Train")

print(len(playlist))
print("Blue Train" in playlist)
print(playlist[0])

for track in playlist:
    print(" -", track)

With four small methods, Playlist supports len(), in, indexing and for loops. Nothing registered it as a collection — Python simply asks the object whether it can do these things.

Operators

Adding your own types together Python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y

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

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

    def __sub__(self, other):
        return Vector(self.x - other.x, self.y - other.y)

    def __mul__(self, factor):
        return Vector(self.x * factor, self.y * factor)

    def __abs__(self):
        return (self.x ** 2 + self.y ** 2) ** 0.5

a = Vector(1, 2)
b = Vector(3, 4)

print(a + b)
print(b - a)
print(a * 3)
print(abs(b))

Each operator returns a new Vector rather than modifying either operand — which is how + behaves everywhere else in Python, and therefore what a reader will expect.

Do not surprise people

Operator overloading is a sharp tool. + on two vectors is obvious. + on two Employee objects is a riddle. If the meaning is not immediately clear to someone who has never seen your class, write a named method instead.

A quick reference

MethodTriggered by
__init__Thing(...)
__str__ / __repr__print(x) / repr(x)
__len__len(x)
__eq__, __lt__==, <, sorting
__contains__in
__getitem__x[key]
__iter__for item in x
__add__, __sub__, __mul__+, -, *
__enter__ / __exit__with x:
__call__x(...)
Exercise

A temperature type

Write a Temperature class holding degrees Celsius. Give it a __repr__ like Temperature(21.5), a __str__ like 21.5°C, equality, ordering, and addition of two temperatures.

class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    # Your __repr__, __str__, __eq__, __lt__ and __add__ here

warm = Temperature(21.5)
cold = Temperature(4.0)

print(warm, cold)              # should read 21.5°C 4.0°C
# print(warm > cold)           # uncomment once __lt__ exists
# print(sorted([warm, cold]))
Show one solution
class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def __repr__(self):
        return f"Temperature({self.celsius})"

    def __str__(self):
        return f"{self.celsius}°C"

    def __eq__(self, other):
        return self.celsius == other.celsius

    def __lt__(self, other):
        return self.celsius < other.celsius

    def __add__(self, other):
        return Temperature(self.celsius + other.celsius)

warm = Temperature(21.5)
cold = Temperature(4.0)

print(warm, cold)
print(warm > cold)          # Python derives > from __lt__ on the other operand
print(sorted([warm, cold]))
print(warm + cold)

warm > cold works with only __lt__ defined because Python tries the reflected operation: cold < warm.

What you learned

  • Built-in syntax dispatches to dunder methods on your objects.
  • __repr__ is the one to write first; __str__ is the human-facing version.
  • __eq__ plus __lt__ gives you equality, ordering and sorting.
  • __len__, __contains__, __getitem__ and __iter__ make a class behave like a collection.
  • Overload operators only where the meaning is obvious.
Last updated on January 10, 2026

Was this article helpful?

Your response is saved on this device.