Summarizing Your Data

A Compact Form: List Comprehensions


Learning Objectives

  • You can read a list comprehension and write the equivalent explicit loop.
  • You can write a list comprehension that extracts one field from each row.
  • You can write a list comprehension that filters rows by a condition.
  • You can recognise which loops in your existing code are good candidates for a comprehension rewrite, and which are not.

You have written a lot of loops in this part. Many of those loops maintain running state, such as streaks, best-so-far values, or per-category summaries. A few of them have done something much simpler: walked a list and appended one new value per item.

Python has a one-line form for that simpler kind of loop. It is called a list comprehension. A list comprehension is a compact spelling of a specific loop shape, not a different kind of loop. You can always rewrite a comprehension as an explicit loop, and you can always rewrite a loop that fits the shape as a comprehension.

The Loop Shape Comprehensions Help With

Consider a small piece of code from the streaks chapter:

days = []
for day_string in day_strings:
days.append(date.fromisoformat(day_string))

The loop body does exactly one thing — a single append of a transformed value. No counter, no accumulator, no condition, no second action. Just walk the list, transform each item, collect the results.

This is the shape comprehensions are for. The same loop, written as a comprehension:

days = [date.fromisoformat(day_string) for day_string in day_strings]

Read it left to right:

  • date.fromisoformat(day_string) — this is what each element of the new list will be.
  • for day_string in day_strings — this is the same loop header as the explicit version.

Simple list building

0 / 5 points

Which loop is a good candidate for a list comprehension?

Three Small Shapes

There are three shapes of comprehension that are useful. All are versions of the same idea, with different combinations of mapping and filtering.

Extracting one field

Walk the sessions, collect the minutes column:

minutes_list = [minutes for subject, minutes, day in sessions]

Equivalent explicit loop:

minutes_list = []
for subject, minutes, day in sessions:
minutes_list.append(minutes)

This is the simplest case — no filter, just a transformation.

Filtering rows

Walk the sessions, keep only the long ones:

long_sessions = [row for row in sessions if row[1] >= 60]

Equivalent:

long_sessions = []
for row in sessions:
if row[1] >= 60:
long_sessions.append(row)

When you keep whole rows like this, the loop header uses a single name (row) rather than unpacking, because what you append is the whole row, not a field.

Both at once

Extract one field, but only from rows that match a condition:

math_minutes = [minutes for subject, minutes, day in sessions if subject == "math"]

Equivalent:

math_minutes = []
for subject, minutes, day in sessions:
if subject == "math":
math_minutes.append(minutes)

This third form captures the two most common things you do to a list: filter it, then pull out a field.

Route names with a comprehension

0 / 5 points

A route log row has this shape: [route, minutes, day].

Which function uses a list comprehension to return route names for rows whose minutes are at least 30?

Loops That Should Stay Loops

The comprehension forms above all have one property: the body of the equivalent loop is a single append. The moment the loop is doing more than that, the comprehension form gets harder to write (and understand), if it is possible to write at all.

When you take a look at the loops you have already written in this part, you will see that most of them are not candidates for a comprehension rewrite. A few examples:

The per-category summary loop. Two accumulators, one condition, a final calculation per category. This is the summaries_by_subject shape:

for subject in subjects_seen:
count = 0
total = 0
for row_subject, minutes, day in sessions:
if row_subject == subject:
count += 1
total += minutes
average = total / count
result.append([subject, count, total, average])

The body of the inner loop updates two accumulators. The body of the outer loop computes an average and appends. Neither one is a single-append loop. Trying to force this into comprehensions makes the code substantially harder to read.

The streak loop. A single-pass walk with running state — current and longest — and a condition that updates them differently depending on whether the streak continues or resets:

current = 1
longest = 1
for index in range(1, len(days)):
if (days[index] - days[index - 1]).days == 1:
current += 1
else:
current = 1
if current > longest:
longest = current

The loop maintains state across iterations. It does not build a new list. A comprehension cannot express this kind of stateful computation.

The “best so far” loop. It walks a list, keeps a running best, updates conditionally. Like the streak loop, it maintains state across iterations and does not build a list:

best = rows[0]
for row in rows[1:]:
if row[1] > best[1]:
best = row

Anything with print. A loop whose body prints something to the user is doing something to the world, not building a list. If the loop requires printing, use a regular loop.

A comprehension fits when the loop’s only job is to build a list by appending one value per iteration. Anything more, and the loop is the right tool.

Stateful summaries

0 / 5 points

Why is a longest-streak loop usually clearer as a normal loop than as a list comprehension?

Rewriting a Few Earlier Examples

The small fluency exercise this chapter teaches is recognising the loops in your existing code that could be rewritten as comprehensions, and doing the rewrite when it actually helps. A few candidates from earlier in the part:

The distinct_days second loop. Conversion-only, single-append per item:

# Before:
days = []
for day_string in day_strings:
days.append(date.fromisoformat(day_string))
# After:
days = [date.fromisoformat(day_string) for day_string in day_strings]

Test helpers. A helper that builds a list of session triples from a list of dates:

# Before:
def make_sessions(date_strings):
result = []
for day in date_strings:
result.append(["math", 30, day])
return result
# After:
def make_sessions(date_strings):
return [["math", 30, day] for day in date_strings]

Extracting one column. The “minutes-only” extraction:

# Before:
all_minutes = []
for subject, minutes, day in sessions:
all_minutes.append(minutes)
# After:
all_minutes = [minutes for subject, minutes, day in sessions]

Comprehensions as Compression

From the point of view of this course, a comprehension is a compression of a particular loop shape. When the loop is already simple, the compression is a clarity win. When the loop is doing several things, the compression makes the code harder to read, not easier.

Sometimes comprehensions are treated as a marker of being a better programmer — “real Python programmers use comprehensions, and explicit loops are for beginners”. This is wrong. Use whatever form makes the code easier for the next reader. Sometimes that is a comprehension. Often it is not.


Check Your Understanding

  1. Write the comprehension form of:
    doubled = []
    for x in numbers:
    doubled.append(x * 2)
  2. Write the comprehension form of:
    even = []
    for x in numbers:
    if x % 2 == 0:
    even.append(x)
  3. Why is the per-category summary loop (summaries_by_subject) not a candidate for a comprehension?
  4. If a loop has a print statement inside it, should you turn that into a comprehension?

AI Study Aid

Create a chapter diagram

We're looking into to what extent AI-generated diagrams could help with studying.

Use this study aid to generate an AI-generated visual summary of the material you just studied. Each diagram style emphasizes the content in a different way, so you can choose the focus that feels most useful.

Using the diagram generator is voluntary and does not affect your course progress. We encourage you to try it out and see if it helps, but it's totally up to you! Your answers help us understand how to make better study aids in the future.

Diagrams tried: 0