Today's outcomes
- Translate an ordinary loop into a comprehension.
- Distinguish filtering from conditional expressions.
- Flatten nested data without sacrificing readability.
Before you start: Complete Day 13 and its practical challenge.
Understand the concept
A comprehension is a readable expression for a new list
A list comprehension combines an output expression, iteration, and optionally a filter. [minutes * 2 for minutes in values] transforms each input value into one output value. The resulting list is new; the comprehension does not automatically edit the source list.
Start from a loop you already understand. Identify the iteration source, the condition that decides whether to keep an item, and the expression appended to the output. Then rewrite those three parts in the comprehension's order. If you cannot explain the original loop, shortening it will not improve your understanding.
A comprehension eagerly builds the list. It is not a promise of constant memory or universal performance improvement. For a large stream, a generator expression or another processing approach may be more suitable. Today's fixtures are deliberately small enough to inspect completely.
Filtering and conditional output are different
An if clause after the for filters items out: [x for x in values if x >= 0]. A conditional expression before the for chooses an output for every item: [x if x >= 0 else 0 for x in values]. The first changes the number of outputs; the second keeps the count but replaces some values.
That distinction matters for missing data. Filtering out None means the output contains only recorded observations. Replacing None with zero means the output includes an imputed value. The two transformations can produce different averages and coverage measures even when both run without errors.
Nested clauses follow the order of nested loops
A flattening comprehension such as [item for row in rows for item in row] corresponds to an outer loop over rows and an inner loop over each row's items. Read the for clauses from left to right when reconstructing the execution.
Nested comprehensions are useful when the operation remains easy to name. If the expression includes several complex conditions, exception handling, logging, or state changes, use an ordinary loop. Readability is a correctness tool because someone must later verify which inputs were transformed or excluded.
Worked example
sessions = [30, None, 0, 60]
recorded = [minutes for minutes in sessions if minutes is not None]
doubled = [minutes * 2 for minutes in recorded]
display = ["missing" if minutes is None else f"{minutes} min"
for minutes in sessions]
rows = [[10, 20], [], [30]]
flattened = [minutes for row in rows for minutes in row]
assert recorded == [30, 0, 60]
assert doubled == [60, 0, 120]
assert display == ["30 min", "missing", "0 min", "60 min"]
assert flattened == [10, 20, 30]
assert sessions == [30, None, 0, 60]
print("Recorded:", recorded)
print("Display:", display)
| Expression role | Output count | Reason |
|---|---|---|
| Recorded filter | 3 | One missing observation excluded |
| Doubled transformation | 3 | One output per recorded observation |
| Display conditional | 4 | Every original observation gets a label |
| Flattening | 3 | Empty row contributes no inner items |
The known zero survives the filter. Using if minutes would exclude both None and zero, contradicting the recorded-observation contract. Concise syntax does not choose the correct missing-value rule for you.
Guided practice
- Predict: compare a filter keeping x >= 10 with a conditional expression replacing smaller values with zero. Use
[0, 5, 10, 15]and record both output lengths. - Modify: build labels only for sessions at least 60 minutes. Keep the missing-value guard before comparing the value with the threshold. Expect one label for the original fixture.
- Reconstruct: write the ordinary nested loops corresponding to the flattening example. Then compare their result with the comprehension. Include an empty row and an entirely empty outer list.
A good naming test is useful: can you describe the transformation in one short sentence? If the sentence contains several unrelated actions, the code may need several steps rather than one impressive-looking comprehension.
Daily challenge
Implement session_labels(rows, threshold=30). The input is a list of lists containing nonnegative integer minutes or None. Flatten in row order, keep recorded values at least the threshold, and return strings such as "30 min". Leave every input row unchanged.
| Rows | Threshold | Expected labels |
|---|---|---|
[[30, None], [0, 60], []] |
30 | ['30 min', '60 min'] |
[[0, 30]] |
0 | ['0 min', '30 min'] |
[] |
30 | [] |
[[None], []] |
30 | [] |
Produce an ordinary-loop implementation first. Use it to explain the policy, then write the comprehension version. Compare both with the independently listed expected results; one implementation agreeing with another is not sufficient if they share the same conceptual mistake.
Reference solution
Compare after attempting the challenge
def session_labels(rows, threshold=30):
return [f"{minutes} min"
for row in rows
for minutes in row
if minutes is not None and minutes >= threshold]
assert session_labels([[30, None], [0, 60], []]) == ["30 min", "60 min"]
assert session_labels([[0, 30]], threshold=0) == ["0 min", "30 min"]
assert session_labels([]) == []
assert session_labels([[None], []]) == []
source = [[30, None], [60]]
assert session_labels(source) == ["30 min", "60 min"]
assert source == [[30, None], [60]]
print("Comprehension contract passed")
The output expression comes first in source spelling, but it is evaluated for items admitted by the following clauses. This is why a reader should reconstruct the for and if logic before reasoning about the formatted output.
Debug and explain
| Mistake | What changes | Diagnostic case |
|---|---|---|
| Truthiness instead of explicit None check | Known zeros disappear | Threshold zero |
| Conditional expression mistaken for filter | Rejected items remain as replacements | Compare lengths |
| For clauses reversed | Different order or unavailable name | Two distinct rows |
| In-place mutation in an output expression | Caller state changes | Snapshot the source |
Explain how many outputs each original item creates, which condition controls admission, and why the empty inner row contributes nothing. If the comprehension is still difficult to read, retain the loop version as the production implementation. The learning goal is accurate transformation, not fewer lines.
Review and next step
Keep both implementations and the four acceptance fixtures. Rebuild the loop first during your next review. Day 15 applies comprehension syntax to mappings, where duplicate keys introduce a new correctness question.
Sources and verification
Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.
Check your understanding
Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.
Review your practical work
Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.
Pass the knowledge check and finish the practical self-review to save completion.