Course outline · 0% complete

0/27 lessons0%

Course overview →

Dunder methods: __repr__, __eq__, __len__

lesson 3-3 · ~13 min · 9/27

Teaching Python about your objects

Print a Player right now and you get noise like <__main__.Player object at 0x102f4b0>. Compare two equal-looking players with == and you get False. Python does not know what your class means yet.

You teach it with dunder methods (double-underscore, like __init__). Python calls them for you at the right moments:

You writePython calls
print(p) or repr(p)p.__repr__()
p == qp.__eq__(q)
len(p)p.__len__()
p + qp.__add__(q)

__repr__ should return a string that looks like the code to rebuild the object, like Player('mia', 50). It makes debugging and printing lists of objects dramatically nicer.

Dunder methods are the reason built-in types feel seamless: len(text), a + b, x == y all route through them. Implementing the same hooks is how your classes plug into that machinery instead of fighting it.

Readable printing and value equality

With __repr__ and __eq__ defined, printing produces something a human can read, and == compares the values inside the objects instead of asking whether they are the same object.

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 self.x == other.x and self.y == other.y

a = Point(2, 3)
b = Point(2, 3)
print(a)
print([a, b])
print(a == b)

Output

Point(2, 3)
[Point(2, 3), Point(2, 3)]
True

The middle line is the one that pays off in real debugging. Printing a list of objects uses each item's __repr__, so without it you would see a row of <__main__.Point object at 0x104f2b3d0> entries telling you nothing about the contents.

Making + work on your own type

Defining __add__ teaches Python what the + operator means for your class. Even the built-in sum joins in, because sum is just repeated + starting from a given value, which is what the Money(0) second argument provides.

class Money:
    def __init__(self, amount):
        self.amount = amount

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

    def __add__(self, other):
        return Money(self.amount + other.amount)

print(Money(5) + Money(7))
print(sum([Money(1), Money(2), Money(3)], Money(0)))

Output

Money(12)
Money(6)

Notice that __add__ builds and returns a new Money rather than modifying either operand. That matches how + behaves for numbers and strings, where a + b never changes a, and it is the convention users of your class will assume.

__len__

Playlist holds songs in a list, so it makes sense for len(playlist) to report the song count. __len__ provides that, and __repr__ gives the object a readable form like Playlist('road trip', 2 songs).

class Playlist:
    def __init__(self, name):
        self.name = name
        self.songs = []

    def add(self, song):
        self.songs.append(song)

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

    def __repr__(self):
        return f"Playlist('{self.name}', {len(self.songs)} songs)"

p = Playlist("road trip")
p.add("Mr. Blue Sky")
p.add("Dog Days Are Over")
print(len(p))
print(p)

Output

2
Playlist('road trip', 2 songs)

__len__ must return an integer, and here it simply delegates to len(self.songs). __repr__ must return a string, built with an f-string that includes literal quotes around the name. Nothing else needs wiring: len(p) starts working the instant __len__ exists, because the built-in len is defined to call it.

The default equality compares identity

Without a custom __eq__, Point(2, 3) == Point(2, 3) returns False.

The equality Python gives you for free compares object identity, meaning it is only True when both sides are literally the same object in memory. Two separately constructed points are two distinct objects, so the comparison fails even though their contents match.

Defining __eq__ replaces that behavior with comparison by value, which is almost always what you want for small data-holding classes like points, money amounts, and coordinates.