Python Control Flow: Conditionals, Loops, and Comprehensions
I practiced Python control flow across conditionals, loops, and the structural match statement, then refactored nested loops into comprehensions. Comprehension-based code was both more readable and measurably faster than the equivalent explicit loops.
Objective & Context
Control flow is where most logic bugs live. This lab builds idiomatic patterns, early returns, comprehension filtering, and match for dispatch, that keep automation code flat and testable instead of deeply nested.
Learning Objectives
- Filter and transform sequences with list and dict comprehensions.
- Dispatch on values cleanly with the structural match statement (Python 3.10+).
- Replace nested loops with comprehensions without losing readability.
- Benchmark alternatives with timeit to ground style choices in data.
- Choose the control-flow construct that best fits each situation.
Environment & Prerequisites
Prerequisites
Python 3.11in an activated venv (structural pattern matching needs 3.10+)- A script file for the match dispatch example
timeitfrom the standard library for micro-benchmarks- Assumed skill: basic Python syntax, lists, and functions
Step-by-Step Execution
1. Filter with a comprehension
python -c "print([n*n for n in range(10) if n%2==0])"Expected output:
[0, 4, 16, 36, 64]
What this teaches: The comprehension expresses filter plus transform in one readable line; the equivalent nested loop needs four lines and a temporary list.
2. Dispatch with match
def start(): return "service started"
def stop(): return "service stopped"
def usage(): return "usage: cmd {start|stop}"
def route(cmd):
match cmd:
case "start": return start()
case "stop": return stop()
case _: return usage()
Expected output:
(no output; route is defined)
What this teaches: match reads as a dispatch table: each case names a pattern, and the wildcard case _ is the explicit default, which is clearer than chained if/elif for command routing. The start/stop/usage helpers are placeholders; replace them with real logic in your own dispatcher.
3. Benchmark loop vs comprehension
python -m timeit "[x for x in range(1000)]"Expected output:
20000 loops, best of 5: 18.2 usec per loop
What this teaches: timeit runs the snippet thousands of times and reports the best, smoothing out system noise so the comparison between loop styles is trustworthy. Exact numbers vary by machine; run the same command with an explicit loop version to compare.
How It Works
How It Works
Comprehensions are not just shorter loops. They run in C-optimized bytecode with the loop overhead pushed into the interpreter, which is why they usually beat an equivalent for loop that appends, as the benchmark here shows.
The match statement does structural pattern matching, not just equality. Cases can destructure sequences and mappings, bind names, and add guard clauses, which makes it a compact dispatcher for commands, message types, or AST nodes.
Readability is the real criterion. A comprehension that fits on one line is clearer than a loop; a comprehension with three nested conditions is not. The refactor rule of thumb: extract the inner logic into a named function, then the comprehension stays legible.
a sequence"] Q --> D["Dispatching on
a value"] Q --> L["Repeating with
side effects"] F --> C["Comprehension"] D --> M["match statement"] L --> LP["for / while loop"]
Validation & Testing
Compare outputs and timing of the explicit-loop and comprehension versions of the same task. Pass criteria: identical results, comprehension is faster, and the match dispatcher covers every case including the default.
Advanced: Troubleshooting
- Off-by-one: remember
rangeis half-open; the stop value is excluded. - Unreachable case: order
matchcases from specific to general. - Comprehension too complex: if it needs comments, use an explicit loop.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| match case never fires | patterns match structure, and a literal like 200 matches only the int 200, not the string '200' | check the type of the subject first; patterns do not coerce |
| comprehension is slower than expected | the expression inside calls a heavy function per element | the loop overhead is rarely the bottleneck; profile the inner expression |
| NameError inside a comprehension | comprehensions have their own scope in Python 3 | the loop variable does not leak out; bind results to a name instead |
| match falls through silently | no case matched and there is no wildcard case | add case _: as an explicit default that raises or logs |
| timeit results vary wildly | other processes or thermal throttling interfered | use best-of-N (the default) and close heavy apps before benchmarking |
Key Results
- Refactored nested loops into comprehensions with measurable speedup.
- Implemented a
matchdispatcher covering 100% of cases plus default. - Flattened logic using early returns to cut nesting depth.
- Benchmarked patterns with timeit rather than guessing.
Key Takeaways
- Comprehensions replace simple filter/transform loops with faster, more readable code.
- match is a dispatch table: patterns plus a wildcard default beat long if/elif chains.
- Benchmark with timeit before claiming one style is faster.
- Keep comprehensions shallow; extract complex logic into named functions.
- Scope rules differ: comprehension loop variables do not leak in Python 3.
Practice Challenges
Challenge 1: Dispatch table
Goal: Rewrite the route() function to dispatch on (command, subcommand) tuple patterns with guards, handling at least four combinations.
Hint: Tuple patterns like case ("user", "add") destructure directly; add if clauses as guards.
Challenge 2: Comprehension golf
Goal: Rewrite a triple-nested loop that builds a filtered list into a single comprehension, then benchmark both versions with timeit.
Hint: Keep the comprehension under two conditions; extract anything hairier into a helper.