When you write for item in my_object, Python needs to know how to get items from your object. The iterator protocol defines this contract - implement iter and next and your custom classes work seamlessly with for loops, list(), and all iteration tools.

Infinite Iterators

basic.py
# Basic iterator


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

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value


# Use the iterator
counter = 
for num in counter:
    print(num)

# Basic iterator


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

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value


# Use the iterator
counter = 
for num in counter:
    print(num)

# Basic iterator


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

    def __iter__(self):
        return self

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 1
        return value


# Use the iterator
counter = 
for num in counter:
    print(num)

manual.py
# Using iter() and next()


data = [10, 20, 30]

# Get iterator manually
it = iter(data)  # calls data.__iter__()

# Get values manually
print(next(it))  # calls it.__next__() -> 10
print(next(it))  # -> 20
print(next(it))  # -> 30

# StopIteration on exhaustion
try:
    print(next(it))  # iterator exhausted
except StopIteration:
    print("Iterator exhausted")

iterable_vs_iterator.py
# Iterable vs iterator


class NumberList:
    """Iterable (not an iterator)"""

    def __init__(self, numbers):
        self.numbers = numbers

    def __iter__(self):
        return NumberIterator(self.numbers)


class NumberIterator:
    """Iterator for NumberList"""

    def __init__(self, numbers):
        self.numbers = numbers
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.index >= len(self.numbers):
            raise StopIteration
        value = self.numbers[self.index]
        self.index += 1
        return value


# Use the iterable multiple times
numbers = NumberList([10, 20, 30])

print("First iteration:")
for n in numbers:
    print(n)

print("\nSecond iteration:")
for n in numbers:
    print(n)

custom_range.py
# Custom range-like iterator


class MyRange:
    def __init__(self, start, stop, step=1):
        self.current = start
        self.stop = stop
        self.step = step

    def __iter__(self):
        return self

    def __next__(self):
        if (self.step > 0 and self.current >= self.stop) or (
            self.step < 0 and self.current <= self.stop
        ):
            raise StopIteration
        value = self.current
        self.current += self.step
        return value


# Use like built-in range
print("Forward:")
for i in MyRange(0, 10, 2):
    print(i, end=" ")

print("\n\nBackward:")
for i in MyRange(10, 0, -2):
    print(i, end=" ")

infinite.py
# Infinite iterator


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

    def __iter__(self):
        return self

    def __next__(self):
        value = self.current
        self.current += 1
        return value


# Infinite iterator (use break to stop)
counter = InfiniteCounter(1)
for num in counter:
    print(num)
    if num >= 5:
        break

Why It Matters

Understanding the iterator protocol helps you:

  • Create custom iterables
  • Understand how for loops work
  • Debug iteration issues
  • Implement lazy evaluation
iterator protocol - the __iter__() and __next__() methods that let objects work with for loops
iterable - an object with __iter__() that returns an iterator; can be iterated multiple times
StopIteration - the exception that signals the end of iteration

Exercise: practical.py

Create a circular buffer iterator that loops through elements