Chefs, Sous-chefs, and Lazy Pipelines
By now youβve met:
- The buffet (iterable: the source of food π±)
- The waiter (iterator: walks forward πΆββοΈ)
- The autopilot waiter (generator: powered by
yield π€
)
But real restaurants need teams sous-chefs, assistants, conveyor belts, and clever tricks to keep food moving.
Today we explore advanced iteration: itertools
, yield from
, generator delegation, cloning with tee
, and lazy pipelines.
And this time, weβll tie it to real-world mini-projects so you see the practical magic.
π΄ 1. itertools β Kitchen Gadgets
Think of itertools
as Pythonβs drawer of restaurant gadgets simple tools that make iteration effortless.
Fun Example: Endless Breadsticks
import itertools
for bread in itertools.count(1): # waiter counts forever
if bread > 5:
break
print(bread)
π Output:
1
2
3
4
5
Behind the curtain:
-
count
is just a tiny iterator with a counter. - Each
next()
increments, no giant list needed.
π Real-World Mini-Project: Streaming Logs
import itertools
def read_logs(filename):
with open(filename) as f:
for line in f:
yield line.strip()
# Look only at the first 5 errors lazily
logs = read_logs("server.log")
errors = (l for l in logs if "ERROR" in l)
for e in itertools.islice(errors, 5):
print("π¨", e)
π You can process gigantic logs without loading them all into memory.
π₯‘ 2. yield from
β Waiter Delegation
When a waiter has too many trays, he calls an assistant:
βHey intern, serve these dishes for me.β
Example:
def menu():
yield 1
yield 2
yield from [3, 4] # delegation
Output β [1, 2, 3, 4]
.
Behind the curtain:
-
yield from iterable
expands into afor
loop. - It even forwards exceptions and return values.
π Real-World Mini-Project: Nested Config Loader
def load_base():
yield "db=sqlite"
yield "timeout=30"
def load_dev():
yield from load_base()
yield "debug=true"
print(list(load_dev()))
# ['db=sqlite', 'timeout=30', 'debug=true']
π yield from
= clean delegation of tasks (like dev configs building on base configs).
π 3. Generator Delegation (Sous-Chefs)
Restaurants donβt have one chef for all courses. They delegate: appetizers, mains, desserts.
def appetizer():
yield "salad π₯"
yield "soup π"
def main_course():
yield "steak π₯©"
yield "pasta π"
def dessert():
yield "cake π°"
def full_meal():
yield from appetizer()
yield from main_course()
yield from dessert()
print(list(full_meal()))
Output β ['salad π₯','soup π','steak π₯©','pasta π','cake π°']
.
π Real-World Mini-Project: File Processing Pipeline
def read_lines(path):
with open(path) as f:
for line in f:
yield line
def parse_csv(lines):
for line in lines:
yield line.strip().split(",")
def filter_users(rows):
for row in rows:
if int(row[2]) > 30: # age > 30
yield row
def users_over_30(path):
yield from filter_users(parse_csv(read_lines(path)))
for u in users_over_30("users.csv"):
print(u)
π This is a real pipeline: one generator hands off to the next like sous-chefs in a line.
π₯ 4. itertools.tee
β Cloning Waiters
Normally: one waiter, one trip. If Alice eats, Bob gets an empty plate.
tee
= photocopy the waiter so both can eat.
import itertools
it = iter([1, 2, 3])
alice, bob = itertools.tee(it)
print(list(alice)) # [1, 2, 3]
print(list(bob)) # [1, 2, 3]
Behind the curtain:
-
tee
buffers values if one consumer lags behind. - Watch out: memory grows if consumers are very out of sync.
π€ Real-World Mini-Project: Dual Consumers (Analytics + Logging)
orders = (f"Order {i}" for i in range(1, 6))
# Clone the stream
chef, cashier = itertools.tee(orders)
print("Chef prepares:", list(chef))
print("Cashier logs:", list(cashier))
π Same data stream, used by two different subsystems.
π§ 5. Lazy Pipelines β Conveyor Belts of Food
Now the coolest trick: combine everything into a conveyor belt where dishes flow step by step.
import itertools
nums = range(1, 1000000)
pipeline = itertools.islice(
(n**2 for n in nums if n % 2), # odd squares
5
)
print(list(pipeline)) # [1, 9, 25, 49, 81]
Behind the curtain:
- Each stage is lazy β nothing happens until
next()
is called. - You can chain infinite data sources safely.
π Real-World Mini-Project: Live Order Filter
import itertools
def infinite_orders():
n = 1
while True:
yield f"Pizza #{n}"
n += 1
orders = itertools.islice(
(o for o in infinite_orders() if "3" not in o), # skip unlucky 3s
5
)
print(list(orders))
Output:
['Pizza #1', 'Pizza #2', 'Pizza #4', 'Pizza #5', 'Pizza #6']
π Streaming infinite orders, but filtered + capped safely.
π¨ ASCII Mental Model
[ Buffet ] β [ Waiter ] β [ Gadget ] β [ Filter ] β [ Map ] β [ Eater ]
Think of it as a restaurant conveyor belt each chef only touches one dish at a time, dishes move lazily, no giant tray dumped all at once.
π¬ Wrap-Up
Today we upgraded from one waiter to a whole restaurant team:
- itertools: kitchen gadgets (count, cycle, chain, islice).
- yield from: waiter delegation.
- generator delegation: sous-chefs working in harmony.
- tee: cloning waiters with buffering.
- lazy pipelines: conveyor belts of infinite food.
π Next up (Part 4): Coroutines waiters who donβt just serve, but can also take your orders mid-service (send
, throw
, async/await
).
Top comments (0)