📘 Lesson 21 · Advanced

Python Iterators & Generators

Learn the iterator protocol, build custom iterators, and use generators with yield.

The Iterator Protocol

Every for loop in Python secretly uses the iterator protocol. When you loop over a list, Python calls iter() to get an iterator object, then repeatedly calls next() on it to get each value. A StopIteration exception signals the end. You can do this manually too — understanding it helps you write custom iterables.

iter.py
nums = [10, 20, 30]
it = iter(nums)    # get an iterator
print(next(it))    # first value
print(next(it))    # second value
print(next(it))    # third value
# next(it) now would raise StopIteration
▶ Output
10
20
30

Custom Iterator Class

Any class implementing __iter__ (returns self) and __next__ (returns next value or raises StopIteration) can be used in a for loop.

counter.py
class CountUp:
    def __init__(self, start, stop):
        self.n = start; self.stop = stop
    def __iter__(self): return self
    def __next__(self):
        if self.n > self.stop: raise StopIteration
        val = self.n; self.n += 1; return val

for n in CountUp(1, 5):
    print(n, end=" ")
▶ Output
1 2 3 4 5

Generators — A Simpler Way

Generators are much easier to write than full iterator classes. Use yield instead of return in a function. Each call to next() runs the function until the next yield, then pauses, saving the function's state. This is lazy evaluation — values are created on demand, not all at once.

fibonacci.py
def fibonacci(n):
    a, b = 0, 1
    for _ in range(n):
        yield a          # pause here, return a
        a, b = b, a + b  # resume here next call

for num in fibonacci(8):
    print(num, end=" ")
▶ Output
0 1 1 2 3 5 8 13
Use generators for large datasets — they generate values one at a time and never hold everything in memory.

🧠 Quick Check

Which keyword makes a function into a generator?

return
generate
yield
iter

Tags

iteratorgeneratoryield__iter____next__lazy evaluationinfinite sequence