Syllabus Lesson 60 of 239 · Pythonic Code, Testing & Capstone
Pythonic Code, Testing & Capstone

Generators and Lazy Sequences

A normal function builds a whole list in memory and hands it back. A generator produces values one at a time, only when asked. You write one by using yield instead of return.

def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

for value in count_up_to(3):
    print(value)   # 1, then 2, then 3

Each time the loop asks for the next value, the function runs until the next yield, hands back that value, and pauses right there, remembering all its local state. The next request resumes from that line.

Why bother? Laziness. A generator can describe a huge or even endless sequence without ever holding it all at once:

def squares(numbers):
    for n in numbers:
        yield n * n

g = squares([1, 2, 3])
print(next(g))   # 1
print(next(g))   # 4
print(list(g))   # [9]  (the rest)

Anything that loops works on a generator: for, list(), sum(), max(). You can also write a quick one inline with a generator expression, which is like a list comprehension with parentheses:

total = sum(n * n for n in range(5))   # 0+1+4+9+16 = 30

Rule of thumb: if you only need to iterate once and the data could be large, prefer a generator.

Your turn

Write a generator function evens(n) that yields the even numbers from 0 up to but not including n, in order. So list(evens(7)) is [0, 2, 4, 6]. It must use yield (do not just build and return a list).

Spotted a problem in this lesson? Report it

Code · runs in your browser
Output