"Dunder" (double underscore) methods are how Python objects hook into built-in syntax and functions — __init__ for construction is the one everyone knows, but many operators and built-ins are dunder methods underneath:
class Vector:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"Vector({self.x}, {self.y})" # unambiguous, dev-facing representation
def __eq__(self, other):
return self.x == other.x and self.y == other.y
def __add__(self, other):
return Vector(self.x + other.x, self.y + other.y)
def __len__(self):
return int((self.x ** 2 + self.y ** 2) ** 0.5)
v1, v2 = Vector(1, 2), Vector(3, 4)
v1 + v2 # Vector(4, 6) — calls v1.__add__(v2)
v1 == Vector(1, 2) # True — calls v1.__eq__
print(v1) # Vector(1, 2) — calls __repr__ since __str__ isn't defined
len(v1) # 2 — calls __len__
| Syntax | Dunder method called |
|---|---|
obj + other | __add__ |
obj == other | __eq__ |
obj[key] | __getitem__ |
len(obj) | __len__ |
str(obj) / print(obj) | __str__ (falls back to __repr__) |
for x in obj | __iter__ |
with obj: | __enter__ / __exit__ |
__repr__ vs. __str____repr__ should be an unambiguous, ideally-eval-able representation aimed at developers (used by the console/debugger and as the fallback for print() when __str__ isn't defined); __str__ is a human-readable representation aimed at end users. Defining __repr__ alone covers both cases reasonably; defining only __str__ leaves debugging output (in a list, in a debugger, in a traceback) showing the unhelpful default <Vector object at 0x...>.