Python Docs

Iterators

The Python iterator protocol consists of two methods: __iter__(), which returns the iterator object itself, and __next__(), which returns the next item or raises StopIteration when there are no more items.

Built-in Iterators

Most built-in containers like list, tuple, dict and set return an iterator when you call iter() on them.

it = iter([1, 2, 3])
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# next(it) -> StopIteration

Custom Iterator Class

You can implement your own iterator by defining __iter__ and __next__. __iter__ should return the iterator object (usually self).

class CountDown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self

    def __next__(self):
        if self.current < 0:
            raise StopIteration
        value = self.current
        self.current -= 1
        return value

for x in CountDown(3):
    print(x)  # 3, 2, 1, 0

Iterables vs Iterators

  • Iterable: Has __iter__() that returns a new iterator each time (e.g. lists, ranges).
  • Iterator: Implements both __iter__() and __next__() and is usually one-shot.
  • One-shot: Once consumed, an iterator is typically exhausted and cannot be reused.
nums = [1, 2, 3]
it1 = iter(nums)
it2 = iter(nums)  # new, independent iterator

print(next(it1))  # 1
print(next(it2))  # 1  (separate state)

Tools: itertools

The itertools module provides building blocks for creating fast, memory-efficient iterator pipelines.

import itertools as it

data = [1, 2, 3, 4, 5]

print(list(it.accumulate(data)))              # Running totals
print(list(it.compress("ABCDE", [1, 0, 1, 0, 1])))  # A C E
print(list(it.islice(range(10), 2, 8, 2)))          # 2 4 6

Best Practices

  • Prefer generators for custom iteration logic — they're simpler than writing full iterator classes.
  • Do not assume an iterator can be reused; create a fresh iterator when you need to iterate again.
  • Let for loops handle StopIteration implicitly instead of calling next() manually.