Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

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 !=); return NotImplemented for unknown types.
  • __len__ powers len() 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.