Generators
What is a generator?
Section titled “What is a generator?”A generator is a function that can pause its execution and resume from where it left off. Instead of computing all values and returning them in a list, a generator produces values one at a time — only when asked. This is called lazy evaluation.
The key ingredient is the yield keyword.
When Python encounters yield, it pauses the function, hands the value to the caller, and freezes the local state (variables, the instruction pointer).
The next call to next() resumes from that exact line.
def count_up(start: int, stop: int): current = start while current <= stop: yield current # pause here and emit current current += 1 # resume here on the next next() call
gen = count_up(1, 4)print(next(gen)) # 1print(next(gen)) # 2print(next(gen)) # 3print(next(gen)) # 4# next(gen) would now raise StopIterationGenerator functions vs regular functions
Section titled “Generator functions vs regular functions”| Aspect | Regular function | Generator function |
|---|---|---|
| Returns | Once, with return | Many times, with yield |
| State | Discarded after return | Frozen between yields |
| Memory | Builds full result | One value at a time |
| Type | The return value | A generator object |
A function becomes a generator function the moment it contains even one yield statement.
Calling it does not execute any code — it returns a generator object immediately.
iter() and next()
Section titled “iter() and next()”Every generator object implements the iterator protocol: it has __iter__() and __next__() methods.
next(gen)advances to the nextyieldand returns that value.- When there are no more values,
next(gen)raisesStopIteration. - A
forloop callsnext()automatically and catchesStopIterationto stop.
def three_words(): yield "Python" yield "is" yield "expressive"
# Equivalent — for loop handles next() and StopIteration for youfor word in three_words(): print(word)Generator expressions
Section titled “Generator expressions”Just as list comprehensions build lists, generator expressions build generators — using parentheses instead of brackets. They are memory-efficient because no list is materialised.
squares_list = [x ** 2 for x in range(1_000_000)] # allocates a listsquares_gen = (x ** 2 for x in range(1_000_000)) # allocates almost nothing
# Consume lazilyfor sq in squares_gen: if sq > 25: breakGenerator expressions compose naturally with built-ins that accept iterables:
total = sum(x ** 2 for x in range(10)) # no brackets needed inside sum()Infinite generators
Section titled “Infinite generators”Because generators are lazy, you can define sequences that never end — as long as the consumer stops asking.
def fibonacci(): a, b = 0, 1 while True: # infinite loop — but never blocks yield a a, b = b, a + bFull runnable demo
Section titled “Full runnable demo”from itertools import islice
def fibonacci(): """Infinite Fibonacci sequence generator.""" a, b = 0, 1 while True: yield a a, b = b, a + b
# Take only the first 10 values — safe because islice stops earlyfirst_10 = list(islice(fibonacci(), 10))print("First 10 Fibonacci numbers:")print(first_10)
# Generator expression: squares of even numberseven_squares = (x * x for x in range(1, 11) if x % 2 == 0)print("\nSquares of even numbers 1-10:")for val in even_squares: print(val, end=" ")print()
# Demonstrate single-use naturegen = (x * 3 for x in range(5))print("\nFirst pass:", list(gen))print("Second pass:", list(gen)) # exhausted — empty list
# Manual next() / StopIterationcounter = iter(range(3))print("\nManual iteration:")print(next(counter))print(next(counter))print(next(counter))try: print(next(counter))except StopIteration: print("StopIteration raised — generator exhausted")Loading Python runtime (first run only)…