Dunder Methods that Matter

The special methods that make your objects print well, compare, iterate and act like built-ins.

Beyond the arithmetic operators, a handful of dunder methods decide how well your class integrates with the rest of Python. Implement the right ones and your object prints cleanly, works in for loops, and behaves in collections.

The essential four

  • __repr__ — the unambiguous, developer-facing string. If you write only one, write this. It is what the REPL and debuggers show.
  • __str__ — the friendly, user-facing string (falls back to __repr__).
  • __eq__ — value equality (pair with __hash__).
  • __len__ — makes len(obj) work and gives truthiness for free.
class Point:
    def __init__(self, x, y):
        self.x, self.y = x, y
    def __repr__(self):
        return f'Point(x={self.x}, y={self.y})'

print(Point(1, 2))          # Point(x=1, y=2)
print([Point(0, 0)])        # [Point(x=0, y=0)]  <- repr used in containers

Making an object iterable and callable

__iter__ lets your object drive a for loop; __contains__ powers in; __call__ makes an instance callable like a function; __getitem__ enables obj[key].

Example

Example · python
class Playlist:
    def __init__(self, name, tracks):
        self.name = name
        self._tracks = list(tracks)

    def __repr__(self):
        return f'Playlist({self.name!r}, {self._tracks!r})'

    def __len__(self):                       # len() and truthiness
        return len(self._tracks)

    def __getitem__(self, i):                # indexing AND iteration
        return self._tracks[i]

    def __contains__(self, track):           # the 'in' operator
        return track in self._tracks

    def __call__(self, track):               # instance acts like a function
        self._tracks.append(track)
        return self

pl = Playlist('Focus', ['Aria', 'Nocturne'])
pl('Prelude')('Etude')                       # __call__, chained

print('repr    :', repr(pl))
print('len     :', len(pl))                  # 4
print('first   :', pl[0])                     # __getitem__
print('in?     :', 'Aria' in pl)              # __contains__
print('loop    :', [t.upper() for t in pl])  # iterable via __getitem__
print('truthy? :', bool(pl))                  # True, from __len__

# Output:
# repr    : Playlist('Focus', ['Aria', 'Nocturne', 'Prelude', 'Etude'])
# len     : 4
# first   : Aria
# in?     : True
# loop    : ['ARIA', 'NOCTURNE', 'PRELUDE', 'ETUDE']
# truthy? : True

When to use it

  • A custom container implements __iter__ and __next__ so it works natively in for loops.
  • A domain model object implements __hash__ alongside __eq__ so it can be stored in a set.
  • A query builder implements __getattr__ to return a new filtered query for any attribute access.

More examples

__iter__ and __next__ for iteration

Implements the iterator protocol with __iter__ and __next__ so the object works in for loops and list().

Example · python
class CountUp:
    def __init__(self, stop):
        self.current = 0
        self.stop = stop
    def __iter__(self): return self
    def __next__(self):
        if self.current >= self.stop:
            raise StopIteration
        self.current += 1
        return self.current

print(list(CountUp(5)))   # [1, 2, 3, 4, 5]

__eq__ and __hash__ together

Defines __eq__ and a matching __hash__ so equal Points compare as one element in a set or dict.

Example · python
class Point:
    def __init__(self, x, y): self.x, self.y = x, y
    def __eq__(self, other): return (self.x, self.y) == (other.x, other.y)
    def __hash__(self): return hash((self.x, self.y))

p1, p2 = Point(1, 2), Point(1, 2)
print(p1 == p2)          # True
print({p1, p2})          # {Point} — only one element

__getitem__ for subscript access

Enables [] access and 'in' membership testing by implementing __getitem__ and __contains__.

Example · python
class Config:
    def __init__(self, data): self._d = data
    def __getitem__(self, key): return self._d[key]
    def __contains__(self, key): return key in self._d

cfg = Config({'host': 'localhost', 'port': 5432})
print(cfg['host'])         # localhost
print('port' in cfg)       # True

Discussion

  • Be the first to comment on this lesson.