Dunder Methods
How __init__, __str__, __eq__, and other double-underscore methods hook your classes into Python's built-in syntax.
2 min read
"Dunder" is short for double underscore — methods like __init__ and __str__ that Python calls automatically in response to built-in syntax, rather than you calling them by name. They're how a custom class plugs into things like print(), ==, len(), and arithmetic operators.
str and repr
By default, printing an object gives you an unhelpful memory address:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
p = Point(3, 4)
print(p) # <__main__.Point object at 0x7f8...>Defining __str__ controls what print() and str() show:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f"Point({self.x}, {self.y})"
p = Point(3, 4)
print(p) # Point(3, 4)__repr__ is a close cousin, meant for an unambiguous, developer-facing representation (shown in a REPL or inside a list of objects). If you only define one, define __repr__ — Python falls back to it for str() too when __str__ is missing.
eq for comparison
Without it, == compares by identity (are these the exact same object in memory), which usually isn't what you want for value-like classes:
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
print(Point(1, 2) == Point(1, 2)) # True -- same coordinatesWithout __eq__, that same comparison would return False, since they'd be two distinct objects in memory despite holding equal data.
len and getitem
These let a custom class respond to len() and indexing (obj[0]) the same way a list does:
class Playlist:
def __init__(self, songs):
self.songs = songs
def __len__(self):
return len(self.songs)
def __getitem__(self, index):
return self.songs[index]
p = Playlist(["Song A", "Song B", "Song C"])
print(len(p)) # 3
print(p[1]) # Song BArithmetic dunders
class Vector:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Vector({self.x}, {self.y})"
v = Vector(1, 2) + Vector(3, 4)
print(v) # Vector(4, 6)Defining __add__ is what makes + work between two Vector objects at all — without it, Python raises a TypeError since it has no idea how to add two custom objects together.
The pattern to remember
You'll rarely call a dunder method directly (p.__str__() works but is unidiomatic) — instead, define it once and let Python's built-in syntax (print(), ==, len(), +) trigger it automatically. This is what lets custom classes feel like a natural part of the language instead of a bolted-on afterthought.
Test what you just learned
4 quick questions. Get all of them right to unlock the next lesson.
You can take the quiz without an account — logging in just lets your result count toward your progress.