We close the module with Python's most characteristic construct. In the previous lessons you'll have noticed a recurring pattern: creating a new list by traversing another one — the catalog prices with VAT, the uppercase titles for the labels, the books still in stock. That three-or-four-line pattern (create an empty list, traverse, transform or filter, append) is so frequent that Python dedicates a syntax of its own to it: list comprehensions, which condense it into a single readable line. Mastering when to use them — and when not to — is one of the hallmarks of "Pythonic" code.

Contents

  1. The pattern we're going to compress
  2. Basic syntax: [expression for element in iterable]
  3. Filtering with if at the end
  4. Transforming with if/else in the expression
  5. Nested comprehensions (in moderation)
  6. Comprehension vs. loop: a head-to-head comparison
  7. When NOT to use a comprehension
  8. A glimpse of dictionary and set comprehensions

The pattern we're going to compress

Ana wants the list of catalog prices with book VAT included. With what you know from the loops lesson, you'd do it like this:

BOOK_VAT = 0.04
prices = [12.50, 9.95, 15.90]

prices_with_vat = []                      # 1. empty list to start with
for price in prices:                      # 2. traverse the original
    prices_with_vat.append(price * (1 + BOOK_VAT))   # 3. transform and append

print(prices_with_vat)

Output:

[13.0, 10.348, 16.536]

Here append() makes its appearance, the method that adds an element to the end of a list — we'll study it in detail in module 4; for now that idea is enough. The full pattern always has the same three pieces: empty list → loop → append of a transformation. Python lets you express exactly that in one line.

Basic syntax: [expression for element in iterable]

The same list of prices with VAT, as a comprehension:

BOOK_VAT = 0.04
prices = [12.50, 9.95, 15.90]

prices_with_vat = [price * (1 + BOOK_VAT) for price in prices]

print(prices_with_vat)   # [13.0, 10.348, 16.536]

Anatomy of the line, from the inside out:

Piece In the example Role
for element in iterable for price in prices The same loop header you already know
expression price * (1 + BOOK_VAT) What gets stored in the new list for each element
[ ... ] the square brackets They say "build a list out of all this"

It reads almost naturally: "a list of price * (1 + VAT) for each price in prices". The writing order is surprising at first (the expression goes before the for), but it reflects what you care about: first what you get, then where it comes from.

More quick examples with Papyrus material:

titles = ["The Odyssey", "Hamlet", "Don Quixote"]

# Uppercase titles for the shelf labels
labels = [title.upper() for title in titles]
# ['THE ODYSSEY', 'HAMLET', 'DON QUIXOTE']

# Length of each title (to work out the width of the menu table)
lengths = [len(title) for title in titles]
# [11, 6, 11]

# The first 5 receipt numbers of the day
receipts = [f"T-{n:03d}" for n in range(1, 6)]
# ['T-001', 'T-002', 'T-003', 'T-004', 'T-005']

(upper() converts a string to uppercase; like lower() in the previous lesson, it's a str method we'll cover in depth in module 4.) Look at the third example: the iterable can be a range, a string, another list... anything you can traverse with for. And the expression can be as simple as the element itself ([x for x in data], which copies the list) or as elaborate as an f-string.

Filtering with if at the end

Second piece: sometimes you don't want to transform all the elements, but rather keep only some of them. Add an if after the for:

[expression for element in iterable if condition]

Only the elements that meet the condition make it into the new list. The Papyrus classic: which books are in stock?

titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"]
stocks = [4, 6, 8, 0]

available = [titles[i] for i in range(len(titles)) if stocks[i] > 0]
print(available)   # ['The Odyssey', 'Hamlet', 'Don Quixote']

And combining transformation + filter in a single comprehension — prices with VAT, but only for the books that cost less than 15 EUR:

BOOK_VAT = 0.04
prices = [12.50, 9.95, 15.90]

cheap_with_vat = [p * (1 + BOOK_VAT) for p in prices if p < 15]
print(cheap_with_vat)   # [13.0, 10.348]

It reads: "the price with VAT, for each p in prices, if p < 15". The filter is applied first (does this element get in?) and the expression afterwards (what do I keep from it?). Don Quixote (15.90) doesn't pass the filter, so its VAT isn't even calculated.

In the if you can use everything from lesson 02-01: compound conditions (if p < 15 and p > 10), truthiness (if title to discard empty strings), not...

answers = ["Hamlet", "", "Faust", ""]

# Clean empty entries out of an order form
valid_orders = [a for a in answers if a]
print(valid_orders)   # ['Hamlet', 'Faust']

Transforming with if/else in the expression

Careful, this case gets confused with the previous one a lot. The if at the end filters (some elements don't get in). But if you want all the elements to get in, choosing between two values depending on a condition, what you need is the ternary operator from lesson 02-01, placed in the expression:

[value_if_true if condition else value_if_false for element in iterable]

Example: the Papyrus menu listing must show all the titles, flagging the ones out of stock.

titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"]
stocks = [4, 6, 8, 0]

listing = [
    titles[i] if stocks[i] > 0 else f"{titles[i]} (OUT OF STOCK)"
    for i in range(len(titles))
]
print(listing)
# ['The Odyssey', 'Hamlet', 'Don Quixote', 'Faust (OUT OF STOCK)']

The two positions of the if, face to face:

Form Position of the if else? Effect Size of the resulting list
[expr for x in data if cond] After the for Not allowed Filters elements ≤ the original
[a if cond else b for x in data] In the expression (ternary) Mandatory Chooses each element's value Same as the original

Mnemonic: if without else at the end = doorman (decides who gets in); if with else at the front = transformer (decides what each one turns into). And yes, both can be combined in the same comprehension, although readability starts to suffer:

# Member discount for the cheap ones, VAT for all, and the out-of-stock ones out
MEMBER_DISCOUNT = 0.05
prices = [12.50, 9.95, 15.90, 21.00]
stocks = [4, 6, 8, 0]

final_prices = [
    round(p * (1 - MEMBER_DISCOUNT), 2) if p < 15 else round(p, 2)
    for i, p in enumerate(prices)
    if stocks[i] > 0
]
print(final_prices)   # [11.88, 9.45, 15.9]

Notice too that a long comprehension can (and should) be split across several lines, aligning the for and the if — it's still a single expression between brackets. And that enumerate() works inside comprehensions just like in ordinary loops.

Nested comprehensions (in moderation)

Just as loops nest, a comprehension allows several fors in a row. It's equivalent to nested loops read left to right (the first for is the outer loop):

# All the format-language combinations Papyrus can order from the distributor
formats = ["hardcover", "paperback"]
languages = ["es", "en"]

combinations = [f"{fmt} ({language})" for fmt in formats for language in languages]
print(combinations)
# ['hardcover (es)', 'hardcover (en)', 'paperback (es)', 'paperback (en)']

Its loop equivalent, so you can see the exact correspondence:

combinations = []
for fmt in formats:                 # first for of the comprehension
    for language in languages:      # second for of the comprehension
        combinations.append(f"{fmt} ({language})")

Another use of nesting is a comprehension inside another, for example to generate a tabular structure (a list of lists):

# Location grid: 2 aisles x 3 shelves
locations = [[f"A{a}-S{s}" for s in range(1, 4)] for a in range(1, 3)]
print(locations)
# [['A1-S1', 'A1-S2', 'A1-S3'], ['A2-S1', 'A2-S2', 'A2-S3']]

Honest warning: two fors in a comprehension is the reasonable limit. Three or more, or nesting with filters and ternaries at the same time, produces lines that nobody (not even you a month from now) deciphers at first glance. In those cases, the classic nested loops from lesson 02-02 are the better tool. Compressing isn't the goal; communicating is.

Comprehension vs. loop: a head-to-head comparison

Let's put the two versions side by side with the complete Papyrus example — final prices for Luis (a member) of the available books:

MEMBER_DISCOUNT = 0.05
BOOK_VAT = 0.04
prices = [12.50, 9.95, 15.90, 21.00]
stocks = [4, 6, 8, 0]

# ---- Loop version: 5 lines ----
luis_prices = []
for i, p in enumerate(prices):
    if stocks[i] > 0:
        final = p * (1 - MEMBER_DISCOUNT) * (1 + BOOK_VAT)
        luis_prices.append(round(final, 2))

# ---- Comprehension version: 1 expression ----
luis_prices = [
    round(p * (1 - MEMBER_DISCOUNT) * (1 + BOOK_VAT), 2)
    for i, p in enumerate(prices)
    if stocks[i] > 0
]

print(luis_prices)   # [12.35, 9.83, 15.71] in both cases

What does each version win?

Criterion Classic for loop Comprehension
Lines of code More Fewer
Reads like A step-by-step recipe A declaration: "I want this list"
Intermediate steps (debug prints, several operations) Easy to add Don't fit
break/continue Available Don't exist inside a comprehension
Side effects (printing, modifying other variables) Natural Contraindicated
Speed Good Slightly higher (the append is optimized internally)

The comprehension's real advantage isn't saving keystrokes: it's declaring intent. [t.upper() for t in titles] says "a list of the titles in uppercase" at a glance; the equivalent loop forces you to read three lines to deduce the same thing. That's why, for the pattern transform/filter a collection into a new list, the comprehension is the preferred form in the Python community.

When NOT to use a comprehension

Knowing how to hold back is as important as knowing how to use them. Signs that it's time to go back to the classic loop:

  • You're not building a list. If the goal is to print, accumulate a total or ask for input(), use a loop. Writing [print(t) for t in titles] works, but it creates a useless list of None and confuses the reader: comprehensions are for producing data, not for doing things.
  • The line doesn't fit or can't be understood. If you need more than two or three pieces (two fors, a filter and a ternary, for instance) or you clearly exceed PEP 8's 79-99 characters even when splitting it, a loop with intermediate names will be clearer.
  • You need to stop halfway or skip elements with complex logic. There's no break or continue in a comprehension; if you miss them, it's a loop.
  • There are several dependent steps. Computing a value, checking it, adjusting it and then storing it are several statements; cramming them into one expression usually requires unreadable tricks.
  • You're debugging. A loop admits intermediate print()s to inspect values; a comprehension doesn't. Sometimes it pays to temporarily "unfold" a comprehension into a loop to investigate a problem and fold it back afterwards.

Final rule: write the comprehension, reread it as if someone else were seeing it and, if you have to think for more than a couple of seconds to understand it, turn it into a loop. Nobody has ever been reproached for a clear loop.

A glimpse of dictionary and set comprehensions

The same idea works with other Python data structures you'll study in module 4. Just as an appetizer:

titles = ["The Odyssey", "Hamlet", "Don Quixote"]
prices = [12.50, 9.95, 15.90]

# DICTIONARY comprehension (braces and a colon): title -> price
price_list = {titles[i]: prices[i] for i in range(len(titles))}
# {'The Odyssey': 12.5, 'Hamlet': 9.95, 'Don Quixote': 15.9}

# SET comprehension (braces without a colon): unique initials
initials = {title[0] for title in titles}
# {'T', 'H', 'D'}

Dictionaries (key-value pairs) and sets (collections without duplicates) deserve their own lessons; when we get to them in module 4, their comprehensions will feel familiar: they're exactly this syntax with {} instead of []. For now, take away that the pattern you've learned today is general.

Common Mistakes and Tips

  • Confusing the filter-if with the ternary if/else: [x for x in data if cond] filters; [a if cond else b for x in data] transforms. Writing [x for x in data if cond else y] is a SyntaxError: the trailing if doesn't allow an else.
  • Using comprehensions for their side effects ([print(x) for x in ...]): technically valid, stylistically wrong. If you're not going to use the resulting list, it's a loop.
  • Shadowing variables: the comprehension's variable is local to it, but if you name it the same as another variable in the program (price = 100 and then [price for price in prices]), the code becomes confusing. Use distinct, descriptive names.
  • Over-nesting: two fors is the comfortable maximum. When in doubt, unfold into loops.
  • Forgetting it produces a new list: the comprehension doesn't modify the original list; prices remains intact after [p * 2 for p in prices]. If you want to keep the result, assign it to a variable.
  • Tip: when a comprehension gets long, split it across several lines inside the brackets (expression / for / if, one piece per line). Python allows it and readability improves enormously.
  • Tip: when reading someone else's comprehension, start with the for (what does it traverse?), continue with the if (what does it let through?) and finish with the expression (what does it build?). That's the order in which it executes.

Exercises

Exercise 1: catalog RRP

Given prices = [12.50, 9.95, 15.90] and the constant BOOK_VAT = 0.04, use a comprehension to create the list rrp (the retail price) with each price including VAT, rounded to 2 decimals (use round(value, 2)). Then, with another comprehension, create member_rrp additionally applying MEMBER_DISCOUNT = 0.05 to the base price, only for base prices below 15 EUR (the rest must not appear in the list).

Exercise 2: window display labels

Given titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"] and stocks = [4, 6, 8, 0], create with a single comprehension the list of window display labels: all the titles in uppercase, and the out-of-stock ones with the suffix " - COMING SOON". Expected result: ['THE ODYSSEY', 'HAMLET', 'DON QUIXOTE', 'FAUST - COMING SOON'].

Exercise 3: from comprehension to loop (and judgement)

This comprehension works, but it's hard to read:

r = [t.upper() if s > 3 else t.lower() for t, s in zip(titles, stocks) if len(t) > 6 and s != 1]

(a) Rewrite it as a classic for loop with descriptive variable names. (b) Explain in one sentence why the loop is preferable in this case. Note: zip() pairs two lists element by element — it's studied in module 3; for this exercise it's enough to know that for t, s in zip(titles, stocks) traverses title and stock at the same time.

Solutions

Solution 1:

BOOK_VAT = 0.04
MEMBER_DISCOUNT = 0.05
prices = [12.50, 9.95, 15.90]

rrp = [round(p * (1 + BOOK_VAT), 2) for p in prices]
print(rrp)          # [13.0, 10.35, 16.54]

member_rrp = [
    round(p * (1 - MEMBER_DISCOUNT) * (1 + BOOK_VAT), 2)
    for p in prices
    if p < 15
]
print(member_rrp)   # [12.35, 9.83]

The first is a pure transformation (same length as the original). The second combines a filter (if p < 15, which discards Don Quixote) and a transformation (discount on the base and then VAT, the same calculation order we used in lesson 02-01).

Solution 2:

titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"]
stocks = [4, 6, 8, 0]

labels = [
    titles[i].upper() if stocks[i] > 0 else titles[i].upper() + " - COMING SOON"
    for i in range(len(titles))
]
print(labels)
# ['THE ODYSSEY', 'HAMLET', 'DON QUIXOTE', 'FAUST - COMING SOON']

Here the if/else goes in the expression (ternary) because all the titles must appear: we're not filtering, we're choosing each one's format. An even cleaner version, applying the suffix with the ternary: [titles[i].upper() + ("" if stocks[i] > 0 else " - COMING SOON") for i in range(len(titles))].

Solution 3:

titles = ["The Odyssey", "Hamlet", "Don Quixote", "Faust"]
stocks = [4, 6, 8, 0]

result = []
for title, stock in zip(titles, stocks):
    if len(title) > 6 and stock != 1:
        if stock > 3:
            result.append(title.upper())
        else:
            result.append(title.lower())

print(result)   # ['THE ODYSSEY', 'DON QUIXOTE']

(b) The loop is preferable because the original comprehension piles four pieces (ternary + for + zip + compound filter) into one line: the reader has to work out what it filters and what it transforms all at once, whereas the loop separates each decision onto its own line with clear names. It's exactly the "when NOT to use a comprehension" criterion: if it takes more than a couple of seconds to understand, unfold it.

Conclusion

With list comprehensions you've come full circle in this module: [expression for x in iterable if condition] gathers into one declarative line what you learned separately — the transformation of a for loop, the filter of an if and the choice of a ternary. You know how to filter (if at the end), transform (if/else in the expression), nest with caution and, above all, recognize when a classic loop communicates better. You've also seen that the same syntax extends to dictionaries and sets, a preview of module 4.

Module 2 ends here, and Papyrus can feel it: its scripts now decide (if/elif/else), repeat (for, while), search and stop (break, loop else), converse through menu.py (while True + match) and generate price lists and labels in a single line. But all that code lives loose, repeated from script to script: the menu's search, the member price calculation, input validation... Every improvement means touching several places. The solution is to package each task under a name of its own and reuse it wherever needed: that's what functions are, the stars of module 3, where we'll reorganize the Papyrus menu piece by piece and you'll discover you've been using functions (print, len, enumerate...) since day one.

Python Programming Course

Module 1: Introduction to Python

Module 2: Control Structures

Module 3: Functions and Modules

Module 4: Data Structures

Module 5: Object-Oriented Programming

Module 6: File Handling

Module 7: Error and Exception Handling

Module 8: Advanced Topics

Module 9: Testing and Debugging

Module 10: Web Development with Python

Module 11: Data Science with Python

Module 12: Final Project

© Copyright 2026. All rights reserved