Iterators and Generators Checkpoint
25 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Fix the mistakes
The slips that cost marks in this module. Spot each one, then check the fix.
Real data work often means reading a stream, cleaning it, and taking a few results. This checkpoint builds one such pipeline and then tests the whole module.
Pick the simplest tool
A generator function is the shortest way to write most iterators. Use an iterator class only when you need extra methods or state that others can read. Use an iterable class with a generator __iter__ when the data must be walked many times.
class Catalog: def __init__(self, products): self.products = products
def __iter__(self): for name, price in self.products: if price > 0: yield nameExample 1 · Reusable catalog class
class Catalog: def __init__(self, products): self.products = products
def __iter__(self): for name, price in self.products: if price > 0: yield name
shop = Catalog([("pen", 10), ("gift", 0), ("book", 90)])print(list(shop))print(list(shop))['pen', 'book'] ['pen', 'book']
What this code does
- Both calls give the same result because each loop starts a new generator.
- A plain generator object would give an empty list the second time.
Each for loop calls __iter__ and gets a fresh generator, so a Catalog can be read again and again.
Chain generators into a pipeline
Each stage takes an iterable and yields results. Values flow through one at a time, so no stage builds a full list.
def read(lines): for line in lines: yield line.strip()
def errors(lines): return (l for l in lines if l.startswith("ERROR"))
log = ["ok 1", "ERROR disk", "ERROR net "]print(list(errors(read(log))))Example 2 · A three-stage pipeline
from itertools import islice
def read(lines): for line in lines: yield line.strip()
def parse(lines): for line in lines: level, _, message = line.partition(" ") yield level, message
def only(level, rows): return (m for lv, m in rows if lv == level)
log = ["INFO start ", "ERROR disk full", "INFO retry", "ERROR net down"]first = list(islice(only("ERROR", parse(read(log))), 1))print(first)['disk full']
What this code does
- Each stage is a generator, so only enough lines are read to find the first error.
- The pipeline stops as soon as
islicehas its one item.
Because every stage is lazy, islice on the end of the pipeline stops all the stages early.
Watch for one-pass and order traps
A generator is used up after one pass. groupby needs sorted data. islice consumes the items it skips. Check each of these before trusting a result.
from itertools import islice
it = iter(range(10))print(list(islice(it, 3)))print(next(it))Example 3 · Iterator consumed by islice
from itertools import islice
it = iter(range(10))print(list(islice(it, 3)))print(next(it))print(list(islice(it, 2)))[0, 1, 2] 3 [4, 5]
What this code does
islicetakes from the same iterator, so the position moves on each time.- Use a new
iter(range(10))when you need to start again.
The second line prints 3, not 0, because islice already consumed the first three items from the same iterator.
Finish up
Mark complete & continue
Next: Closures and Captured Variables