Lesson 10 of 11
You've met __init__, __str__, and __repr__. Dunder methods let your classes work with
Python's built-in operators and functions, so they behave like native types.
__eq__ — the == operator
By default, == on two objects compares identity — are they the very same object?
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
a = Point(1, 2)
b = Point(1, 2)
print(a == b)
That prints False, which is rarely what you want. __eq__ defines what equality means:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
return self.x == other.x and self.y == other.y
a = Point(1, 2)
b = Point(1, 2)
c = Point(3, 4)
print(a == b)
print(a == c)
print(a != b)
Define __eq__ and you get != free — Python infers it. Defining __eq__ also makes in work
on lists of your objects, since in compares with ==.
Be careful with other: if someone writes Point(1, 2) == "hello", that code raises
AttributeError. A robust version checks first:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __eq__(self, other):
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y
print(Point(1, 2) == "hello")
Returning NotImplemented tells Python to fall back to its default, which gives False.
__len__ — the len() function
class Playlist:
def __init__(self):
self.songs = []
def add(self, song):
self.songs.append(song)
def __len__(self):
return len(self.songs)
p = Playlist()
p.add("Song A")
p.add("Song B")
print(len(p))
__len__ also decides truthiness — an object with length 0 is falsy, so if p: works naturally:
class Playlist:
def __init__(self, songs=None):
self.songs = songs or []
def __len__(self):
return len(self.songs)
empty = Playlist()
full = Playlist(["Song A"])
print(bool(empty))
print(bool(full))
__add__ — the + operator
class Money:
def __init__(self, amount):
self.amount = amount
def __add__(self, other):
return Money(self.amount + other.amount)
def __repr__(self):
return f"Money({self.amount})"
a = Money(10)
b = Money(5)
print(a + b)
Notice __add__ returns a new object rather than modifying self — that's how + behaves
everywhere else in Python, and breaking the expectation would surprise people.
__getitem__ — square brackets
class Playlist:
def __init__(self, songs):
self.songs = songs
def __getitem__(self, index):
return self.songs[index]
def __len__(self):
return len(self.songs)
p = Playlist(["A", "B", "C"])
print(p[0])
print(p[-1])
for song in p:
print(song)
__getitem__ gives you indexing — and for free, your object becomes iterable, so for loops
work. Module 9 covers the fuller iterator protocol.
The common ones
| Method | Enables |
| --- | --- |
| __init__ | creating the object |
| __str__ | print(), str() |
| __repr__ | REPL, debugging, containers |
| __eq__ | == and != |
| __len__ | len(), truthiness |
| __add__ | + |
| __getitem__ | obj[i], iteration |
| __contains__ | in |
Don't add these reflexively. Define one when the operator has an obvious meaning for your type —
adding two Money objects is clear; "adding" two BankAccount objects is not.
Try it yourself
Give Point equality and Playlist a length.
Checkpoint
- Dunder methods hook classes into built-in operators and functions.
__eq__defines==(and gives you!=); returnNotImplementedfor unknown types.__len__powerslen()and truthiness.__add__should return a new object, not mutate.__getitem__gives indexing and makes the object iterable.
Questions about this lesson
Log in to ask a question.