List Comprehensions in Python
Learn how to write concise, readable Python code using list comprehensions. This lesson covers the core concepts, hands-on examples, and common edge cases.
Focus: list comprehensions in Python
Have you ever written a for loop to build a list and thought, "There's got to be a cleaner way"? You're not alone. A simple transformation like squaring every number often leads to three lines of boilerplate, obscuring the real intent. List comprehensions in Python eliminate that clutter, letting you express the same logic in a single, readable line — and they often run faster, too.
The problem this lesson solves
Building new lists from existing iterables is one of the most frequent tasks in Python. Without list comprehensions, you typically write a for loop, append items, and end up with code like this:
squares = []
for x in range(10):
squares.append(x ** 2)
print(squares) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
This works, but it's verbose. The intent — "square each number in range(10)" — is buried inside three lines of mechanics (initialize, loop, append). As your data transformations grow more complex, the noise multiplies, making code harder to read, maintain, and debug. List comprehensions solve this by collapsing the pattern into a single, expressive expression.
Core concept / mental model
Think of a list comprehension as a compact factory for lists. It's a syntactic sugar that mirrors the mathematical set-builder notation: { x² | x ∈ ℝ, x > 0 }. In Python, the form is:
[expression for item in iterable if condition]
expression: What you want to compute for each item (e.g.,x ** 2).item: The loop variable.iterable: The sequence you're iterating over.if condition(optional): A filter that keeps only matching items.
Key insight: A list comprehension always produces a new list object. It never modifies the original iterable. Think of it as a copy-and-transform operation.
How it works step by step
Let's dissect a comprehension from the inside out:
Step 1: Start with the outer brackets
[ ... ] — The brackets tell Python you're building a list.
Step 2: Write the expression
Inside, the leftmost part is the output expression. For example, x ** 2 squares each input.
Step 3: Add the for clause
for x in range(10) — This defines the loop. The variable x takes each value from the iterable.
Step 4 (optional): Add a filter
if x % 2 == 0 — Only items that pass the condition are processed. The filter comes after the for clause.
Full example — mapping with a filter
# Get squares of even numbers from 0 to 9
even_squares = [x ** 2 for x in range(10) if x % 2 == 0]
print(even_squares) # [0, 4, 16, 36, 64]
Nested loops
Comprehensions also support nested loops (like nested for loops):
# Flatten a matrix (list of lists)
matrix = [[1, 2], [3, 4], [5, 6]]
flattened = [value for row in matrix for value in row]
print(flattened) # [1, 2, 3, 4, 5, 6]
Notice the order: the outer loop (for row ...) comes first, then the inner loop (for value ...), matching the order you'd write nested for loops.
Hands-on walkthrough
Example 1: Basic mapping
Convert a list of temperatures from Celsius to Fahrenheit:
celsius = [0, 10, 20, 30, 40]
fahrenheit = [(temp * 9/5) + 32 for temp in celsius]
print(fahrenheit) # [32.0, 50.0, 68.0, 86.0, 104.0]
Example 2: Filtering with a condition
Get only the positive numbers from a list:
numbers = [-5, 3, -1, 101, -99, 42]
positives = [n for n in numbers if n > 0]
print(positives) # [3, 101, 42]
Example 3: Applying a function
Strip whitespace and uppercase each string in a list:
words = [' hello ', 'WORLD ', ' python ']
cleaned = [w.strip().upper() for w in words]
print(cleaned) # ['HELLO', 'WORLD', 'PYTHON']
Example 4: Conditional expression (ternary inside)
You can use a ternary inside the expression, but keep it readable:
values = [1, -2, 3, -4, 5]
processed = ['positive' if v > 0 else 'negative' for v in values]
print(processed) # ['positive', 'negative', 'positive', 'negative', 'positive']
Compare options / when to choose what
| Approach | Lines | Readability | Speed | Flexibility |
|---|---|---|---|---|
for loop |
3–5 | Clear for complex logic | Moderate | High |
list comprehension |
1 | Very clear for simple transformations | Fast | Moderate |
map() + filter() |
1–2 | Can be cryptic | Fast | Low |
generator expression |
1 | Good for huge data (lazy) | Low memory | Moderate |
- Use list comprehensions when you need a new list with simple mapping/filtering (most common case).
- Prefer
forloops when the transformation is too complex to fit in a single expression (e.g., multiple side effects, many conditions). - Use generator expressions
(expr for item in iterable)when you don't need the whole list at once — they yield items on demand. - Avoid
map()andfilter()unless you're already usinglambdaand prefer a functional style; comprehensions are more Pythonic in most cases.
Pro tip: If your comprehension has more than two
forclauses or a complex expression, break it into a regularforloop with clear intermediate variables. Comprehensions are about clarity, not cleverness.
Troubleshooting & edge cases
Error: unintended list of None
If you write a comprehension where the expression returns None, you'll get a list full of None values. This often happens when you accidentally call a method that modifies in-place:
# BAD: .sort() returns None
words = ['banana', 'apple', 'cherry']
sorted_words = [w.sort() for w in words] # [None, None, None]
Fix: Use sorted() instead.
Error: variable leakage (Python 2 only — not an issue in Python 3)
In Python 3, the loop variable of a comprehension is local to the comprehension and doesn't leak into the enclosing scope. No need to worry.
Edge case: empty iterable
result = [x * 2 for x in []]
print(result) # []
A comprehension over an empty iterable always returns an empty list. No crash.
Edge case: condition on large data
Filtering with if is fast because it checks each item once. However, if the condition is expensive (e.g., heavy regex), the comprehension still evaluates it for every item. Consider pre-computing the condition or using filter() if you need a lazy approach.
Wrong output: unexpected duplicates
Check that your source iterable doesn't contain duplicates unless you want them. Use set() if you need uniqueness:
data = [1, 2, 2, 3]
# You might expect [2, 6], but:
result = [x * 2 for x in data if x > 1]
print(result) # [4, 4, 6] — includes duplicate 2
What you learned & what's next
You now understand how to use list comprehensions for concise code: the core [expression for item in iterable if condition] pattern, how it relates to for loops, and when to choose it over alternatives. You've practiced mapping, filtering, flattening, and using conditional expressions inside comprehensions. You also know common pitfalls like in-place mutations and the role of empty iterables.
Next lesson: You'll learn to write dictionary comprehensions — the same elegant concept but for building dict objects. This completes the trio of sequence-oriented comprehensions (list, set, dict) and sets you up for functional patterns like map and filter.
Practice recap
Your turn: Write a list comprehension that takes a list of strings like ['hello', 'WORLD', ''] and returns only the non-empty strings with the first letter capitalized. For bonus points, filter out strings that are all uppercase. Run your solution in a Python shell.
Common mistakes
- Forgetting that
list.sort()returnsNone– using it inside a comprehension creates a list ofNonevalues. - Writing a comprehension with side effects (like
print()) inside the expression, which violates the principle of keeping comprehensions for pure transformations. - Using nested comprehensions when a simple
forloop would be easier to read, especially with more than twoforclauses. - Confusing
[expr for x in items if cond](filter) with[expr if cond else other for x in items](conditional expression).
Variations
- Set comprehensions
{expr for item in iterable}– create a set with automatic deduplication. - Dictionary comprehensions
{key_expr: value_expr for item in iterable}– build dictionaries in one line. - Generator expressions
(expr for item in iterable)– lazy evaluation, ideal for large or infinite streams.
Real-world use cases
- Extracting email addresses from a list of user dictionaries:
[user['email'] for user in users if user['active']]. - Normalizing file names in a directory listing:
[f.lower().replace(' ', '_') for f in os.listdir() if f.endswith('.txt')]. - Parsing API response JSON to collect specific fields:
[item['id'] for item in response['data']].
Key takeaways
- List comprehensions replace common
forloop +appendpatterns with a single, readable line. - Syntax:
[expression for item in iterable if condition]– filter is optional. - Comprehensions are faster than manual loops in many cases due to internal optimization.
- Keep comprehensions simple (one
forclause, oneif); useforloops for complex logic. - Comprehensions always produce a new list; they never modify the original iterable.
- Use conditional expressions (
true_val if cond else false_val) inside the expression part, not in the filter.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.