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- •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
Tap a card to flip it and see the answer.