NotesPythonModules & Advanced

Iterators & Generators

What actually powers a for loop, and how to lazily produce values with yield.

Every for loop works because the object being looped over is iterable — it implements __iter__, which returns an iterator: an object with __next__ that produces one value at a time until it raises StopIteration.

numbers = [1, 2, 3]
it = iter(numbers)      # get an iterator from the list
print(next(it))         # 1
print(next(it))         # 2
print(next(it))         # 3
# next(it) now would raise StopIteration

You almost never call iter()/next() directly — a for loop does it for you automatically. Understanding this machinery matters because it explains generators.

Generators — functions that yield instead of return

A generator function uses yield instead of return. Calling it doesn't run the function — it returns a generator object immediately. Each call to next() (or each step of a for loop) resumes the function from exactly where it left off.

def countdown(n):
    while n > 0:
        yield n
        n -= 1
    print("Liftoff!")

for number in countdown(3):
    print(number)
# 3
# 2
# 1
# Liftoff!

Why generators matter: laziness

A generator produces values one at a time, on demand, instead of building the whole collection in memory upfront. This is the difference between a list comprehension and a generator expression — the same syntax with () instead of [].

squares_list = [n * n for n in range(1_000_000)]     # builds all 1,000,000 in memory now
squares_gen = (n * n for n in range(1_000_000))       # builds nothing yet — produces values as asked

print(next(squares_gen))   # 0 -- computed only now
print(next(squares_gen))   # 1

For huge or even infinite sequences, a generator is the only practical option — you couldn't build an infinite list, but you can absolutely have an infinite generator that you only ever partially consume.

def video_ids():
    n = 1
    while True:              # infinite — fine, because nothing forces it to finish
        yield f"CWM-{n:04d}"
        n += 1

ids = video_ids()
print(next(ids))   # CWM-0001
print(next(ids))   # CWM-0002
Key points to remember
  • Iterable means an object has __iter__; iterator means it has __next__ and raises StopIteration when exhausted — a for loop uses both automatically.
  • yield turns a function into a generator — calling it returns a generator object immediately without running the body.
  • A generator resumes exactly where it left off on each next() call, keeping its local state between calls.
  • (x for x in ...) is a generator expression — same syntax family as a list comprehension, but lazy: it computes values on demand instead of building the whole list upfront.
  • Generators can represent infinite sequences safely, since you only ever pull as many values as you actually consume.

A countdown generator

Revision Flashcards

1 / 3

Tap a card to flip it and see the answer.