⚡IntermediateObject-Oriented Python

Special Methods (Dunder Methods)

Making objects behave like built-in types using __str__, __len__, __eq__, __add__, and more.

Overview

Special methods (also called dunder methods short for "double underscore") let your objects integrate seamlessly with Python's built-in operators and functions. When you write `len(obj)`, Python calls `obj.__len__()`. When you write `a + b`, Python calls `a.__add__(b)`. When you write `a == b`, Python calls `a.__eq__(b)`. This protocol is how Python achieves its clean syntax everything from `print()` to `for` loops to arithmetic operators is backed by dunder methods. By implementing the right dunders in your class, you can make your objects behave exactly like built-in types. The most useful dunders for everyday classes are: `__str__` and `__repr__` for display, `__len__` for `len()`, `__eq__` and comparison operators, `__add__`/`__sub__`/etc. for arithmetic, `__getitem__`/`__setitem__` for indexing, and `__iter__`/`__next__` for iteration.

Key Points

  • •`__str__(self)` → called by print() and str() human-readable output
  • •`__repr__(self)` → called in the REPL and repr() unambiguous, developer-facing output
  • •`__len__(self)` → called by len() return an integer
  • •`__eq__(self, other)` → called by ==; `__lt__`, `__le__`, `__gt__`, `__ge__` for comparisons
  • •`__add__`, `__sub__`, `__mul__`, `__truediv__` → arithmetic operators
  • •`__getitem__(self, key)` → enables obj[key]; `__setitem__` enables obj[key] = value
  • •`__contains__(self, item)` → enables `item in obj`; `__iter__` enables `for x in obj`

Examples

__str__, __repr__, __len__, __eq__

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):                # print(p) → "Point(2, 3)"
        return f"Point({self.x}, {self.y})"

    def __repr__(self):               # repr(p) → "Point(x=2, y=3)"
        return f"Point(x={self.x}, y={self.y})"

    def __eq__(self, other):          # p1 == p2
        if not isinstance(other, Point):
            return NotImplemented
        return self.x == other.x and self.y == other.y

    def __len__(self):                # len(p) → distance in whole units
        return int((self.x**2 + self.y**2) ** 0.5)

p1 = Point(3, 4)
p2 = Point(3, 4)
p3 = Point(1, 2)

print(p1)             # Point(3, 4)
print(repr(p1))       # Point(x=3, y=4)
print(p1 == p2)       # True
print(p1 == p3)       # False
print(len(p1))        # 5

Arithmetic operators and __add__

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 __mul__(self, scalar):       # v * 3
        return Vector(self.x * scalar, self.y * scalar)

    def __rmul__(self, scalar):      # 3 * v  (right-side multiplication)
        return self.__mul__(scalar)

    def __str__(self):
        return f"Vector({self.x}, {self.y})"

v1 = Vector(1, 2)
v2 = Vector(3, 4)
print(v1 + v2)     # Vector(4, 6)
print(v1 * 3)      # Vector(3, 6)
print(3 * v1)      # Vector(3, 6)

__getitem__ and __iter__ making a class iterable

class NumberRange:
    def __init__(self, start, end):
        self.data = list(range(start, end))

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

    def __getitem__(self, index):     # allows: obj[i], obj[1:3]
        return self.data[index]

    def __contains__(self, item):     # allows: item in obj
        return item in self.data

r = NumberRange(1, 6)
print(len(r))          # 5
print(r[0])            # 1
print(r[-1])           # 5
print(3 in r)          # True
print(list(r))         # [1, 2, 3, 4, 5] iterable via __getitem__
for n in r:
    print(n, end=" ")  # 1 2 3 4 5

Common Mistakes

  • Defining __eq__ but not __hash__ Python sets __hash__ to None, making the object unhashable (can't be used in sets or as dict keys). Define both or use @dataclass.
  • Returning the wrong type from dunders __len__ must return an int, __bool__ must return a bool
  • Confusing __str__ and __repr__: __str__ is for users, __repr__ is for developers. If only one is defined, Python falls back from __str__ to __repr__.

Note

Use `@functools.total_ordering` to get all comparison operators from just `__eq__` and one of `__lt__`, `__le__`, `__gt__`, `__ge__`. Defining all six manually is tedious and error-prone.

Exercises

Write Python code to solve each exercise. Run it in your browser and check your output.

Loading editor…

Completed: 0 / 5 exercises

Knowledge Check

Question 1 of 4

Which dunder method does `len(obj)` call?