AI Hints
Home›Python Tutorial›Generators, Decorators and Context Managers
Python · Lesson 22 of 49

Iterators and Generators

Produce values only when needed.

⏱ 2 min read▶ Video at 1:09:36🎓 Free
Watch this lesson in the Python courseWatch on YouTube ↗Full Python course

Ava wants to process a log file with ten million lines. If she loads every line into a list, her laptop runs out of memory, and freezes. She needs a way to handle one line at a time, forgetting the previous ones. Python's answer is the generator, a function that produces values on demand, and never holds them all at once.

Iterators and Generators
From the video: Iterators and Generators
  • Ten million lines will not fit in memory
  • Process one item at a time
  • The tool: generators

What a for loop really does

A for loop works with iterators. An iterable, like a list, can give you an iterator, with the iter function. Each call to next returns the following item. When there are none left, a StopIteration is raised, and the for loop quietly ends. Understanding this unlocks the rest of the lesson.

iter_protocol.pypython
plants = ["fern", "ivy"]
it = iter(plants)
print(next(it))
print(next(it))
try:
    next(it)
except StopIteration:
    print("no more items")
Output
fern
ivy
no more items

Your first generator

A generator is a function that uses yield instead of return. Each time it yields, it pauses, remembering where it was, and hands one value to the caller. The next request resumes it right after the yield. Here, a generator counts down. Nothing runs until the loop asks for a value.

gen1.pypython
def countdown(n):
    while n > 0:
        yield n
        n -= 1

for x in countdown(3):
    print(x)
g = countdown(2)
print(next(g), next(g))
Output
3
2
1
2 1

Generators save memory

Here is the memory difference. A list of one million squares takes about eight megabytes. The generator expression, in round brackets, takes almost nothing, because it produces each square only when asked. We can still sum it. When data is large, or endless, generators are the right choice.

gen_memory.pypython
import sys

big_list = [x * x for x in range(1_000_000)]
lazy = (x * x for x in range(1_000_000))
print(sys.getsizeof(big_list) > 1_000_000)
print(sys.getsizeof(lazy) < 500)
print(sum(lazy))
Output
True
True
333332833333500000

A pipeline of generators

Generators combine into pipelines, where each stage passes items to the next, one at a time. Here, the first stage reads lines, the second filters the errors, and the third cleans them up. Memory use stays tiny, no matter how large the log is. This is how professionals process huge data.

pipeline.pypython
def read(lines):
    for ln in lines:
        yield ln

def errors(lines):
    for ln in lines:
        if "ERROR" in ln:
            yield ln

def clean(lines):
    for ln in lines:
        yield ln.strip().lower()

log = ["INFO ok", "ERROR disk full ", "INFO ok", "ERROR timeout "]
for item in clean(errors(read(log))):
    print(item)
Output
error disk full
error timeout

A generator of even numbers

Your turn. Write a generator called evens, that takes a limit, and yields the even numbers from zero up to that limit. Then loop over it, and print them. Ready? Here is one solution. A loop with a step of two, and a yield inside.

✍️ Exercise: A generator of even numbers

Write a generator evens(limit) that yields 0, 2, 4, ... up to limit, and print them.

Show hint

use yield inside a for loop

Show solution
evens.pypython
def evens(limit):
    for n in range(0, limit + 1, 2):
        yield n

for e in evens(10):
    print(e)
Output
0
2
4
6
8
10

Summary

Wonderful. Iterators step through items one at a time, generators produce values on demand with yield, and pipelines of generators handle huge data with tiny memory. Next, we learn how to wrap functions to add features, using closures and decorators.

Key takeaways
  • iter() and next() power every loop
  • yield pauses and resumes a function
  • Generators use constant memory
Next up: Closures and Decorators — add powers to functions