Module 1 · Python · Drills
Comprehensions, generators, decorators, and error handling only stick when your fingers know them. Type every drill before you reveal the solution — effortful recall is the whole point.
Drill 1 comprehension
Rewrite this loop as a single list comprehension that keeps only the even squares:
out = [] for n in range(10): if n % 2 == 0: out.append(n * n)
out = [n * n for n in range(10) if n % 2 == 0] print(out) # [0, 4, 16, 36, 64]
Read it as "expression · for · (optional) if". The filter goes at the end.
Drill 2 dict comp
Given names = ["Sam", "Mei", "Ali"], build a dict mapping each name to its length, e.g. {"Sam": 3, ...}.
names = ["Sam", "Mei", "Ali"] lengths = {name: len(name) for name in names} print(lengths) # {'Sam': 3, 'Mei': 3, 'Ali': 3}
Drill 3 generator
Write a generator chunks(items, size) that yields successive slices of a list. list(chunks([1,2,3,4,5], 2)) should give [[1,2],[3,4],[5]].
def chunks(items, size): for i in range(0, len(items), size): yield items[i : i + size] print(list(chunks([1, 2, 3, 4, 5], 2))) # [[1, 2], [3, 4], [5]]
This is the exact shape you'll use to stream document chunks in the RAG module — lazy, memory-flat.
Drill 4 try/except
Write safe_int(s) that returns int(s), but returns 0 if the string isn't a valid number (catch ValueError). Test it on "42" and "oops".
def safe_int(s): try: return int(s) except ValueError: return 0 print(safe_int("42")) # 42 print(safe_int("oops")) # 0
Catch the specific exception (ValueError), never a bare except:. This is EAFP — try it, handle the failure.
Drill 5 decorator
Write a decorator announce that prints "before" before the function runs and "after" after it, then returns the result. Apply it to a greet(name).
def announce(fn): def wrapper(*args, **kwargs): print("before") result = fn(*args, **kwargs) print("after") return result return wrapper @announce def greet(name): return f"Hi {name}" print(greet("Sam")) # before / after / Hi Sam
Same wrapper shape as FastAPI's @app.get — wrap, add behaviour, return.
Drill 6 Counter
Given tags = ["ai","uae","ai","jobs","ai","uae"], use Counter to print the two most common tags with their counts.
from collections import Counter tags = ["ai", "uae", "ai", "jobs", "ai", "uae"] for tag, n in Counter(tags).most_common(2): print(f"{tag}: {n}") # ai: 3 / uae: 2
log_levels.py: given a list of log lines, count how many of each level (INFO, WARN, ERROR) appeared and print them most-common first. This is the exact ops skim you'll do once DocChat is deployed.
Build · log-level counter
Input:
lines = [
"INFO server started",
"WARN slow query",
"ERROR db lost",
"INFO request 200",
"ERROR embedding timeout",
"INFO request 200",
]
from collections import Counter # comprehension: first word of each line is the level levels = [line.split()[0] for line in lines] for level, n in Counter(levels).most_common(): print(f"{level}: {n}") # INFO: 3 / ERROR: 2 / WARN: 1
The whole job is two ideas: a comprehension to extract the field, a Counter to tally. Extract-then-tally is half of all log and analytics code you'll ever write.
Click a card to flip it. Say the answer out loud before you flip — that's the rep that builds storage strength.
[expr for x in items if cond] — expression, for, optional filter.yield do?@app.get registers a route.Optional[str]?str or None. Modern form: str | None.with open(...)?Counter do?Counter(xs).most_common(n) gives the top n.Tick each only if you can do it without looking:
yield and explain why it's memory-efficient@app.get uses onewith open(...) and a Counter, and read Optional[str]