Comprehensions and generators
Transform collections in one readable line, and stream large datasets without loading them into memory.
Why this matters in AI / ML / GenAI
Comprehensions build token lists, filter chunks, and reshape records constantly. Generators matter more: training data and log files are too big for RAM, so you yield batches instead of building a giant list.
List, dict, and set comprehensions
[expr for item in seq] builds a list. Add a filter: [x for x in scores if x > 0.7].
Dict version: {k: v for k, v in pairs}. Set version uses braces with a single expression.
Rule of thumb: one loop and one condition is fine; anything more nested belongs in a normal for-loop. Readability wins — a reviewer should understand it at a glance.
Generators: lazy sequences
Replace the brackets with parentheses and you get a generator expression — values are produced on demand, not stored.
A generator function uses yield instead of return. Each yield hands one value to the caller and pauses; the next iteration resumes right there.
Why it matters: streaming a 50 GB JSONL of training records with for record in read_records(path) uses constant memory. The same pattern powers token streaming from an LLM, where each chunk arrives one at a time.
Generators are single-use. Once consumed, iterate again by calling the function again.
Copy-paste examples
Copy into your own editor, or load one into the compiler below and press Run.
Clean and filter in one line
Read it as: keep the stripped lowercase text for each doc that is long enough.
docs = [" Python ", "AI", " Machine Learning ", "ML"]
cleaned = [d.strip().lower() for d in docs if len(d.strip()) > 2]
print(cleaned)
lengths = {d.strip(): len(d.strip()) for d in docs}
print(lengths)Generator that yields batches
This is the shape of a data loader. Memory stays flat regardless of dataset size.
def batched(items, size):
batch = []
for item in items:
batch.append(item)
if len(batch) == size:
yield batch
batch = []
if batch:
yield batch
rows = list(range(10))
for i, batch in enumerate(batched(rows, 4), start=1):
print("batch", i, batch)Streaming tokens (simulated)
Real LLM streaming has the same interface: iterate and handle each piece.
def stream_tokens(text):
for word in text.split():
yield word
collected = ""
for token in stream_tokens("Retrieval augmented generation grounds answers"):
collected += token + " "
print("received:", token)
print("final:", collected.strip())Filter retrieved chunks by score
Try it — in-browser Python
Lower the threshold to 0.5 and compare how many chunks survive.
Output
Python runs in your browser. First run downloads the runtime.
Press Run (or Ctrl+Enter) to execute.
CPython in WebAssembly. Stdlib works. NumPy and pandas load on demand. No input(), no GPU, no network installs.
Takeaways
- Comprehensions replace short build-a-list loops; keep them to one loop and one filter.
- Generators use yield and stream data with constant memory.
- Generators are single-use — call the function again to re-iterate.