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:
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:
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
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
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
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
| Method | Triggered 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(...) |
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.