Try the tool first:
Open the Interactive Python Comprehensions Cheat Sheet40+ examples, real-time search, category filtering, one-click copy. 100% client-side, no signup.
Python comprehensions are one of the language's most beloved features — a single, elegant line that replaces multiple lines of boilerplate. Yet developers still hesitate: When should I use a generator instead of a list? How do I write an if-else inside a comprehension? Can I nest comprehensions? Why is my dict comprehension raising a KeyError?
This comprehensive guide covers every Python comprehension concept you need to know — from basic list comprehensions to advanced nested patterns, the walrus operator, and performance tradeoffs. Each section includes practical code examples you can copy and run immediately. By the end, you'll write Pythonic transformations with confidence — and you'll have a free interactive cheat sheet bookmarked for quick reference.
What Are Python Comprehensions?
A Python comprehension is a concise syntax for creating a new iterable (list, dict, set, or generator) from an existing iterable, applying an expression to each element and optionally filtering with a condition. Comprehensions are more compact, more readable, and often faster than equivalent for loops.
# The four types of comprehensions in Python
# 1. List Comprehension — returns a list
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# 2. Dict Comprehension — returns a dictionary
square_dict = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# 3. Set Comprehension — returns a set (unique values)
square_set = {x**2 for x in range(10)}
# {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}
# 4. Generator Expression — returns a lazy iterator
square_gen = (x**2 for x in range(10))
# <generator object> — values computed on demand Key Characteristics
- Concise: One line replaces three to five lines of for-loop boilerplate.
- Fast: Comprehensions run at C speed inside the interpreter, faster than pure Python loops.
- Functional: No side effects — the expression produces a new collection without mutating the source.
- Composable: Multiple for clauses and conditions can be chained for complex transformations.
The readability rule: Comprehensions are great for simple transformations. If your comprehension spans multiple lines or uses deeply nested logic, a regular for loop is usually more readable. Guido van Rossum calls this the "one obvious way" principle.
List Comprehensions
List comprehensions are the most common type. They create a new list by evaluating an expression for each element in an existing iterable.
Basic Syntax
The simplest form: [expression for item in iterable]
# Basic list comprehension
numbers = [1, 2, 3, 4, 5]
doubled = [x * 2 for x in numbers]
# [2, 4, 6, 8, 10]
# With range
squares = [x**2 for x in range(6)]
# [0, 1, 4, 9, 16, 25]
# With string
chars = [c.upper() for c in "hello"]
# ['H', 'E', 'L', 'L', 'O']
# With enumerate (unpacking in the expression)
indexed = [f"{i}: {v}" for i, v in enumerate(["a", "b", "c"])]
# ['0: a', '1: b', '2: c'] Filtering with if
Add a condition after the for clause to filter elements: [expression for item in iterable if condition]
numbers = [1, -2, 3, -4, 5, -6] # Keep only positive numbers positive = [x for x in numbers if x > 0] # [1, 3, 5] # Keep only even numbers evens = [x for x in range(20) if x % 2 == 0] # [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] # Filter strings by length words = ["apple", "kiwi", "banana", "fig"] long_words = [w for w in words if len(w) > 4] # ['apple', 'banana'] # Multiple chained conditions (AND logic) nums = range(100) result = [x for x in nums if x > 10 if x % 2 == 0 if x % 3 == 0] # [12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, 78, 84, 90, 96]
if-else Conditional Expression
Use a ternary expression before the for clause to transform every element conditionally: [expr_if_true if condition else expr_if_false for item in iterable]
numbers = [1, -2, 3, -4, 5] # Replace negatives with 0 clamped = [x if x > 0 else 0 for x in numbers] # [1, 0, 3, 0, 5] # Label numbers as even/odd labels = ["even" if x % 2 == 0 else "odd" for x in range(5)] # ['even', 'odd', 'even', 'odd', 'even'] # Categorize scores scores = [85, 92, 67, 45, 78] grades = ["A" if s >= 90 else "B" if s >= 80 else "C" if s >= 70 else "D" for s in scores] # ['B', 'A', 'D', 'D', 'C']
Multiple for Clauses (Nested Iteration)
Chain multiple for clauses to iterate over nested structures. The rightmost for is the innermost loop.
# Flatten a matrix
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [item for row in matrix for item in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Equivalent to:
# flat = []
# for row in matrix:
# for item in row:
# flat.append(item)
# Cartesian product
colors = ["red", "green"]
sizes = ["S", "M", "L"]
products = [f"{c}-{s}" for c in colors for s in sizes]
# ['red-S', 'red-M', 'red-L', 'green-S', 'green-M', 'green-L']
# Multiple iterables with zip
names = ["Alice", "Bob"]
ages = [30, 25]
pairs = [f"{n} is {a}" for n, a in zip(names, ages)]
# ['Alice is 30', 'Bob is 25'] Using Functions and Methods
words = [" Hello ", "WORLD", " Python "]
# Strip and lowercase
clean = [w.strip().lower() for w in words]
# ['hello', 'world', 'python']
# Using built-in functions
nums = [1.2, 2.7, 3.1]
integers = [round(n) for n in nums]
# [1, 3, 3]
# Object method calls
class User:
def __init__(self, name): self.name = name
def greet(self): return f"Hello, {self.name}!"
users = [User("Alice"), User("Bob")]
greetings = [u.greet() for u in users]
# ['Hello, Alice!', 'Hello, Bob!'] Dict Comprehensions
Dict comprehensions create dictionaries using the syntax {key: value for item in iterable}.
Basic Syntax
# Basic dict comprehension
squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# From two lists using zip
keys = ["a", "b", "c"]
values = [1, 2, 3]
merged = {k: v for k, v in zip(keys, values)}
# {'a': 1, 'b': 2, 'c': 3}
# With enumerate
indexed = {i: v for i, v in enumerate(["apple", "banana"])}
# {0: 'apple', 1: 'banana'} Filtering with if
# Filter by value
scores = {"Alice": 85, "Bob": 92, "Charlie": 67, "Diana": 78}
passed = {name: score for name, score in scores.items() if score >= 80}
# {'Alice': 85, 'Bob': 92}
# Filter by key
config = {"debug_mode": True, "api_key": "secret", "timeout": 30}
public = {k: v for k, v in config.items() if not k.endswith("_key")}
# {'debug_mode': True, 'timeout': 30}
# Multiple conditions
users = {"alice": 25, "bob": 17, "charlie": 30, "diana": 15}
adults = {name: age for name, age in users.items() if age >= 18 if len(name) > 3}
# {'alice': 25, 'charlie': 30} Transforming Existing Dicts
# Invert a dictionary (values become keys)
d = {"a": 1, "b": 2, "c": 3}
inverted = {v: k for k, v in d.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Caution: duplicate values will be lost!
d2 = {"a": 1, "b": 1}
# inverted → {1: 'b'} ('a' is overwritten)
# Convert values
prices = {"apple": 1.5, "banana": 0.8, "cherry": 2.5}
cents = {k: int(v * 100) for k, v in prices.items()}
# {'apple': 150, 'banana': 80, 'cherry': 250}
# Conditional transformation
scores = {"Alice": 85, "Bob": 92, "Charlie": 67}
grades = {
name: ("A" if s >= 90 else "B" if s >= 80 else "C")
for name, s in scores.items()
}
# {'Alice': 'B', 'Bob': 'A', 'Charlie': 'C'} Nested Dict Comprehensions
# Create a multiplication table as nested dict
table = {
i: {j: i * j for j in range(1, 4)}
for i in range(1, 4)
}
# {
# 1: {1: 1, 2: 2, 3: 3},
# 2: {1: 2, 2: 4, 3: 6},
# 3: {1: 3, 2: 6, 3: 9}
# }
# Flatten nested dict (one level)
nested = {"a": {"x": 1, "y": 2}, "b": {"x": 3, "y": 4}}
flat = {f"{outer}_{inner}": val
for outer, inner_dict in nested.items()
for inner, val in inner_dict.items()}
# {'a_x': 1, 'a_y': 2, 'b_x': 3, 'b_y': 4} Set Comprehensions
Set comprehensions create sets (unique, unordered collections) using curly braces with a single expression: {expression for item in iterable}.
# Basic set comprehension
nums = [1, 2, 2, 3, 3, 3, 4]
unique_squares = {x**2 for x in nums}
# {1, 4, 9, 16}
# Deduplicate and normalize
words = ["Apple", "apple", "APPLE", "Banana", "banana"]
unique = {w.lower() for w in words}
# {'apple', 'banana'}
# Filter with condition
even_squares = {x**2 for x in range(20) if x % 2 == 0}
# {0, 4, 16, 36, 64, 100, 144, 196, 256, 324}
# Set comprehension vs set() constructor
from_list = set([x**2 for x in range(10)]) # Creates list first, then set
from_comp = {x**2 for x in range(10)} # Directly creates set — slightly faster Mathematical Operations via Set Comprehension
# Multiples
multiples_3 = {x for x in range(50) if x % 3 == 0}
# Prime numbers (simple sieve)
primes = {x for x in range(2, 50) if all(x % y != 0 for y in range(2, int(x**0.5) + 1))}
# Character sets from strings
vowels = {"a", "e", "i", "o", "u"}
text = "Hello World"
found_vowels = {c.lower() for c in text if c.lower() in vowels}
# {'e', 'o'} Generator Expressions
Generator expressions use parentheses instead of brackets and return a lazy iterator. They evaluate elements one at a time on demand, making them incredibly memory-efficient for large datasets.
Basic Syntax
# Generator expression
gen = (x**2 for x in range(10))
# <generator object>
# Consume with list()
list(gen)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# Consume in a loop (most common)
for val in (x**2 for x in range(10)):
print(val)
# Generator expressions can only be iterated once!
gen = (x**2 for x in range(5))
list(gen) # [0, 1, 4, 9, 16]
list(gen) # [] — exhausted! Memory Efficiency
# List comprehension — stores all values in memory squares_list = [x**2 for x in range(10_000_000)] # Uses ~80MB # Generator expression — almost zero memory squares_gen = (x**2 for x in range(10_000_000)) # Uses ~0MB # Sum without storing all values total = sum(x**2 for x in range(10_000_000)) # Computes each square, adds to sum, discards — memory constant # Find first match without scanning everything first_big = next((x for x in range(1000000) if x**2 > 500000), None) # Stops at x=708 — does not evaluate remaining 999,292 elements
Chaining Generator Expressions
# Chain multiple transformations as generators
nums = range(1000000)
# Step 1: filter positives
positive = (x for x in nums if x > 0)
# Step 2: square them
squared = (x**2 for x in positive)
# Step 3: filter large results
large = (x for x in squared if x > 10000)
# Nothing computed yet! Only when we iterate:
result = list(large)[:5]
# [10201, 10404, 10609, 10816, 11025]
# Or chain in one expression
result = list(
x**2 for x in (x for x in range(1000) if x > 0)
if x**2 > 10000
)[:5] Using next() with Default
# Find first even number
first_even = next((x for x in [1, 3, 5, 8, 9] if x % 2 == 0), None)
# 8
# No match — returns default
first_negative = next((x for x in [1, 3, 5] if x < 0), 0)
# 0
# Default can be any value
first_admin = next(
(u for u in users if u.role == "admin"),
User("default", "guest")
) Conditionals and Logic
Single if Filter
Placed after the for clause. Elements that fail the condition are skipped.
[x for x in items if x > 0]
if-else Expression
Placed before the for clause. Every element is transformed; the condition chooses which expression to use.
[x if x > 0 else 0 for x in items]
Multiple if Filters (AND Logic)
Multiple if clauses after the for clause act as logical AND.
[x for x in range(100) if x > 10 if x % 2 == 0 if x % 3 == 0] # Equivalent to: x > 10 and x % 2 == 0 and x % 3 == 0
Walrus Operator (Python 3.8+)
The := walrus operator lets you assign and test in one expression, perfect for expensive computations or pattern matching inside comprehensions.
import re lines = ["Order 123", "No number here", "Invoice 456", "Error"] # Extract numbers only from lines that contain them numbers = [m.group(0) for line in lines if (m := re.search(r'\d+', line))] # ['123', '456'] # Without walrus (requires two passes or helper function) # With walrus: one pass, clean and Pythonic # Another example: cache expensive computation values = [1, 4, 9, 16, 25] sqrt_results = [s for x in values if (s := x**0.5) > 2] # [2.0, 3.0, 4.0, 5.0]
Nested Comprehensions
Nested comprehensions can be written with multiple for clauses in a single comprehension, or as comprehensions inside comprehensions.
Multiple for Clauses (Preferred)
# Flatten a matrix — multiple for clauses matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] flat = [item for row in matrix for item in row] # [1, 2, 3, 4, 5, 6, 7, 8, 9] # With condition on inner element filtered_flat = [item for row in matrix for item in row if item > 5] # [6, 7, 8, 9] # With condition on outer element first_two = [item for row in matrix if len(row) == 3 for item in row[:2]] # [1, 2, 4, 5, 7, 8]
Nested Comprehension (Comprehension Inside Comprehension)
# Transpose a matrix
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# Equivalent with zip (usually clearer)
transposed = list(map(list, zip(*matrix)))
# Generate Pascal's triangle rows
pascal = [[1 if j == 0 or j == i else row[j-1] + row[j]
for j in range(i + 1)]
for i, row in enumerate([[1]] * 6)]
# Simplified:
def pascal_row(n):
row = [1]
for _ in range(n):
row = [x + y for x, y in zip([0] + row, row + [0])]
return row
[pascal_row(i) for i in range(5)]
# [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]] When to Use Nested vs Flat
# Nested comprehension — result is nested structure matrix = [[i * j for j in range(5)] for i in range(5)] # 5x5 multiplication table # Flat comprehension with multiple for clauses — result is flat coords = [(x, y) for x in range(3) for y in range(3)] # [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
Performance and Best Practices
List Comprehension vs For Loop
import timeit
# For loop
loop_code = '''
result = []
for x in range(10000):
result.append(x**2)
'''
# List comprehension
comp_code = '[x**2 for x in range(10000)]'
# Generator expression
gen_code = 'list(x**2 for x in range(10000))'
print(timeit.timeit(loop_code, number=1000))
# ~0.85 seconds
print(timeit.timeit(comp_code, number=1000))
# ~0.55 seconds — ~35% faster
print(timeit.timeit(gen_code, number=1000))
# ~0.60 seconds — slightly slower than list comp due to generator overhead List Comprehension vs map()
# map() with lambda mapped = list(map(lambda x: x**2, range(10000))) # List comprehension comped = [x**2 for x in range(10000)] # map() is slightly slower in Python 3 because of lambda overhead # List comprehensions are generally preferred for readability # map() can be faster with an existing function: mapped = list(map(str, range(10000))) # str is a built-in
Memory Comparison
import sys
# List comprehension — full list in memory
lst = [x**2 for x in range(1_000_000)]
print(sys.getsizeof(lst)) # ~8,000,000 bytes
# Generator expression — almost zero memory
gen = (x**2 for x in range(1_000_000))
print(sys.getsizeof(gen)) # ~112 bytes
# Dict comprehension
d = {x: x**2 for x in range(1_000_000)}
print(sys.getsizeof(d)) # ~50,000,000 bytes Common Patterns
Frequency Counting
Dict comprehensions are perfect for counting occurrences.
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
# Using dict comprehension with count()
freq = {word: words.count(word) for word in set(words)}
# {'apple': 3, 'banana': 2, 'cherry': 1}
# More efficient with collections.Counter
from collections import Counter
freq = dict(Counter(words))
# Same result, but Counter is O(n) vs O(n²) for count() in comprehension Grouping
from collections import defaultdict
people = [("Alice", "Engineering"), ("Bob", "Sales"), ("Charlie", "Engineering")]
# Group by department using defaultdict
groups = defaultdict(list)
for name, dept in people:
groups[dept].append(name)
# Or with dict comprehension (less efficient for large data)
groups = {dept: [name for name, d in people if d == dept]
for _, dept in people}
# {'Engineering': ['Alice', 'Charlie'], 'Sales': ['Bob']} Flattening Nested Structures
# Flatten list of lists nested = [[1, 2], [3, 4], [5, 6]] flat = [item for sublist in nested for item in sublist] # [1, 2, 3, 4, 5, 6] # Flatten mixed-depth (one level) mixed = [1, [2, 3], [4, [5, 6]]] flat = [item for sub in mixed for item in (sub if isinstance(sub, list) else [sub])] # [1, 2, 3, 4, [5, 6]] — only flattens one level
Removing Duplicates While Preserving Order
items = [3, 1, 2, 3, 2, 4, 1, 5]
# Dict comprehension trick (Python 3.7+ preserves insertion order)
unique = list({x: None for x in items}.keys())
# [3, 1, 2, 4, 5]
# Equivalent but clearer
def unique_ordered(seq):
seen = set()
return [x for x in seq if not (x in seen or seen.add(x))]
unique_ordered(items)
# [3, 1, 2, 4, 5] Dictionary from Object Attributes
class User:
def __init__(self, name, age, role):
self.name = name
self.age = age
self.role = role
user = User("Alice", 30, "Engineer")
# Convert to dict (excluding private attributes)
user_dict = {k: v for k, v in user.__dict__.items() if not k.startswith('_')}
# {'name': 'Alice', 'age': 30, 'role': 'Engineer'} Comparison Tables
List Comprehension vs For Loop vs map/filter
| Feature | List Comp | For Loop | map/filter |
|---|---|---|---|
| Syntax | [x*2 for x in items] | Multi-line | list(map(lambda x: x*2, items)) |
| Speed | Fastest | Slower | Slower (lambda overhead) |
| Readability | Excellent | Good | Poor (lambda) |
| Filtering | Built-in if | if statement | filter() + map() |
| Side effects | Discouraged | Natural | Discouraged |
| Nested loops | Clean | Verbose | Very messy |
List Comp vs Generator vs Set Comp vs Dict Comp
| Feature | List Comp | Generator | Set Comp | Dict Comp |
|---|---|---|---|---|
| Syntax | [...] | (...) | {...} | {k: v} |
| Result | list | iterator | set | dict |
| Memory | O(n) | O(1) | O(n) | O(n) |
| Evaluation | Eager | Lazy | Eager | Eager |
| Reusable | Yes | No (once) | Yes | Yes |
| Duplicates | Kept | Kept | Removed | Last wins |
| Best for | Small/medium data | Large data, one pass | Uniqueness | Key-value mapping |
Pro Tips and Gotchas
Gotcha: Don't use comprehensions for side effects. A comprehension should produce a value. If you find yourself writing [print(x) for x in items] or [f.write(x) for x in items], use a for loop instead. The list of None values is wasteful and confusing.
Tip: Keep comprehensions readable. If a comprehension spans more than one line or uses deeply nested logic, a regular loop with helper variables is usually clearer. Readability counts more than brevity.
Tip: Use generator expressions for large data. When processing files, database results, or streams, generator expressions keep memory usage constant. sum(x.price for x in orders) works on millions of rows without loading them all.
Tip: Walrus operator for pattern matching. Python 3.8's := operator is powerful inside comprehensions: [m.group(0) for line in lines if (m := re.search(r'\d+', line))]. It eliminates the need for two-pass logic or helper functions.
Tip: Avoid complex if-else chains. A comprehension with multiple nested ternary operators is hard to read. If you need x if a else y if b else z if c else w, consider a helper function or a loop with explicit branches.
Tip: Nested comprehensions are not always better. [[row[i] for row in matrix] for i in range(3)] transposes a matrix, but list(map(list, zip(*matrix))) is often clearer. Choose the version that future readers will understand fastest.
Related Resources
- Python List Methods Cheat Sheet — 60+ list operations including slicing, sorting, and stack/queue patterns.
- Python Dictionary Methods Cheat Sheet — Master dict methods, comprehensions, and merging patterns.
- Python Set Methods Cheat Sheet — 40+ set operations including union, intersection, and deduplication.
- Python Tuple Methods Cheat Sheet — Tuple creation, unpacking, namedtuple, and common patterns.
- Python Built-in Functions Cheat Sheet — 70+ built-in functions organized by category.
- Python String Methods Cheat Sheet — 60+ string methods with examples.