A list comprehension says what to produce, which loop supplies the items, and optionally which items survive. The short form is useful, but it has a hard readability limit. These examples show both the grammar and where to stop.
🎙️ Published & recorded: ·
Start in the middle with for n in nums, then read the expression on the left as the output. A filtering if goes at the far right and can drop an item. An if-else goes before the for because it chooses the output value while keeping every item.
nums = [1, 2, 3, 4, 5]
[n * n for n in nums] # transform → [1,4,9,16,25]
[n for n in nums if n % 2 == 0] # filter → [2,4]
[n*n for n in nums if n > 2] # both → [9,16,25]
["even" if n%2==0 else "odd" for n in nums] # choose
The position changes the meaning. Right-side if means keep or discard. Left-side if-else means choose this value or that value.
Good comprehensions perform one recognizable transformation with at most one simple filter. Cleaning non-empty strings and selecting active users' email fields pass that test. Filename filtering also reads naturally because the condition sits beside the loop it controls.
# clean strings and discard blanks
[s.strip().lower() for s in raw if s.strip()]
# pull a field from active API users
[u["email"] for u in users if u["active"]]
# parse numeric fields
[float(x) for x in fields]
# filenames matching a suffix
[f for f in os.listdir(".") if f.endswith(".csv")]
For actual CSV data, do not create fields with line split comma; quoted commas break that shortcut. Feed the row through Python's CSV module first, then use a comprehension to convert the already parsed fields.
Curly braces with a key and value create a dictionary. Curly braces with one expression create a set and remove duplicates. To flatten one nested level, write the for clauses in the same left-to-right order as the equivalent nested loops: first each row, then each item in that row.
{w: len(w) for w in words} # dict: word → length
{u["id"]: u for u in users} # index records by id
{n % 3 for n in nums} # set: unique values
{v: k for k, v in d.items()} # invert a dict
matrix = [[1, 2], [3, 4], [5]]
[x for row in matrix for x in row] # [1,2,3,4,5]
One flattening level is a reasonable ceiling. Add another nested level or a condition and the reader has to mentally execute the code. Write the loops out instead.
The most common failed rewrite puts else after a filtering condition. Python reports SyntaxError: invalid syntax because a right-side filter has no else branch. Move the whole if-else expression to the left of the for. If you only want matching items, delete else and keep the right-side if.
[n for n in nums if n % 2 == 0 else 0]
# SyntaxError: invalid syntax
[ n if n % 2 == 0 else 0 for n in nums ] # choose
[ n for n in nums if n % 2 == 0 ] # filter
sum(n*n for n in nums) # generator; no intermediate list
[print(x) for x in items] allocates a useless list of None; use a normal loop for side effects. Also switch to a loop when there are two conditions, exception handling, or a comment needed to explain the expression.When sum, max, any, or all can consume values directly, remove the square brackets and use a generator expression. It avoids storing a temporary list. Do that for memory, not to squeeze complicated logic onto one line.