Iterators & Generators
Produce values one at a time, lazily, with yield.
Syntax
def gen():
yield valueAn iterator produces items one at a time. A generator is the easiest way to make one: write a function that uses yield instead of return.
Why generators?
They are lazy — values are produced only as needed, so you can work with huge or even infinite sequences without storing them all in memory.
Example
def countdown(n):
while n > 0:
yield n
n -= 1
for number in countdown(3):
print(number)
# A generator expression:
squares = (x * x for x in range(4))
print(list(squares))
# Output:
# 3
# 2
# 1
# [0, 1, 4, 9]When to use it
- A log reader uses a generator to stream lines from a multi-gigabyte file without loading it all into memory.
- A data pipeline yields transformed records one at a time so memory stays constant regardless of dataset size.
- An infinite counter generator produces unique IDs on demand without pre-generating a list.
More examples
Simple generator function
Defines a generator that yields values one at a time instead of returning a list.
def countdown(n):
while n > 0:
yield n
n -= 1
for val in countdown(5):
print(val, end=' ')
# Output: 5 4 3 2 1Generator for large file reading
Yields lines from a file one by one, keeping memory usage constant for arbitrarily large files.
def read_lines(filepath):
with open(filepath) as f:
for line in f:
yield line.rstrip()
for line in read_lines('notes.txt'):
print(line)Generator expression
Creates a lazy generator expression (like a list comprehension with parentheses) that computes values on demand.
numbers = range(1, 1_000_001)
squares_gen = (n ** 2 for n in numbers if n % 2 == 0)
print(next(squares_gen)) # 4
print(next(squares_gen)) # 16
print(sum(squares_gen)) # rest of the evens squared
Discussion