Skip to content

Dunder Methods

Dunder methods (short for double-underscore) are special methods like __repr__, __eq__, and __add__ that Python calls automatically in response to built-in syntax and functions. They are the mechanism behind the Python Data Model — the contract that lets your objects behave like built-in types.

Both produce string representations, but they serve different audiences:

MethodWhen calledPurpose
__repr__repr(obj), interactive shellUnambiguous developer representation
__str__str(obj), print(obj)Human-friendly display

If only __repr__ is defined, Python uses it for both. The convention for __repr__ is to return a string that, if passed to eval(), would recreate the object.

class Point:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Point({self.x}, {self.y})"
def __str__(self) -> str:
return f"({self.x}, {self.y})"

Python calls __eq__ when you write a == b. Without it, == falls back to identity comparison (is) — two separate instances with the same data are never equal.

def __eq__(self, other: object) -> bool:
if not isinstance(other, Point):
return NotImplemented
return self.x == other.x and self.y == other.y

Return NotImplemented (not False) when the types are incompatible — this lets Python try the reflected operation on the other operand.

Implement __len__ to support len(obj). Implement __getitem__ to support obj[index] subscript access. Together they also make the object iterable via a for loop (Python calls __getitem__ with 0, 1, 2, … until IndexError).

def __len__(self) -> int:
return 2
def __getitem__(self, index: int) -> float:
return (self.x, self.y)[index]

Arithmetic operators map to dunder methods:

OperatorMethod
a + ba.__add__(b)
a - ba.__sub__(b)
a * ba.__mul__(b)
a == ba.__eq__(b)
a < ba.__lt__(b)
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
class Vector:
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __str__(self) -> str:
return f"({self.x}, {self.y})"
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __len__(self) -> int:
return 2
def __getitem__(self, index: int) -> float:
return (self.x, self.y)[index]
v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(repr(v1))
print(str(v1))
print(v1 == Vector(1, 2))
print(v1 + v2)
print(len(v1))
print(v1[0], v1[1])
What is the difference between `__repr__` and `__str__`?
When should `__eq__` return `NotImplemented` instead of `False`?
Which dunder method does `v1 + v2` call on `v1`?
If a class implements `__len__` and `__getitem__`, what else does Python allow automatically?