As you write more Python code, you'll find yourself frequently creating lists by transforming or filtering existing data. While you can do this with loops, Python offers a more elegant and efficient solution: list comprehensions. These powerful constructs let you create lists in a single, readable line of code.
Generator expressions take this concept further, creating memory-efficient iterators that produce values on demand. Understanding when to use list comprehensions versus generator expressions will help you write faster, more memory-efficient code. By the end of this lesson, you'll be able to transform data concisely and choose the right tool for each situation.
What You'll Learn
- List comprehension syntax and patterns
- Nested list comprehensions
- Conditional comprehensions
- Generator expressions vs list comprehensions
- Performance considerations
- When to use each approach
Basic List Comprehensions
A list comprehension is a concise way to create lists. The basic syntax is [expression for item in iterable]:
# Traditional approach with a loop
squares = []
for x in range(1, 6):
squares.append(x ** 2)
print(squares) # [1, 4, 9, 16, 25]
# List comprehension (much more concise!)
squares = [x ** 2 for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
# Another example: converting strings to uppercase
names = ["alice", "bob", "charlie"]
upper_names = [name.upper() for name in names]
print(upper_names) # ['ALICE', 'BOB', 'CHARLIE']
List comprehensions are not just shorter—they're also more readable once you understand the syntax. They clearly express the intent: "create a list by applying this expression to each item."
List Comprehensions with Conditions
You can add conditions to filter items using the syntax [expression for item in iterable if condition]:
# Get only even numbers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = [x for x in numbers if x % 2 == 0]
print(evens) # [2, 4, 6, 8, 10]
# Filter words longer than 4 characters
words = ["python", "is", "awesome", "and", "powerful"]
long_words = [word for word in words if len(word) > 4]
print(long_words) # ['python', 'awesome', 'powerful']
# Get squares of only positive numbers
values = [-3, -2, -1, 0, 1, 2, 3]
positive_squares = [x ** 2 for x in values if x > 0]
print(positive_squares) # [1, 4, 9]
You can also use conditional expressions (ternary operators) within comprehensions:
# Convert numbers to strings, marking negatives
numbers = [-2, -1, 0, 1, 2, 3]
labels = [f"negative {abs(x)}" if x < 0 else str(x) for x in numbers]
print(labels) # ['negative 2', 'negative 1', '0', '1', '2', '3']
Nested List Comprehensions
You can nest comprehensions to work with nested data structures:
# Create a 3x3 matrix
matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]
print(matrix)
# [[1, 2, 3], [2, 4, 6], [3, 6, 9]]
# Flatten a nested list
nested = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
flat = [item for sublist in nested for item in sublist]
print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Create pairs from two lists
list1 = [1, 2, 3]
list2 = ['a', 'b', 'c']
pairs = [(x, y) for x in list1 for y in list2]
print(pairs)
# [(1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c'), (3, 'a'), (3, 'b'), (3, 'c')]
Practical Examples
Let's see list comprehensions in real-world scenarios:
# Example 1: Processing user data
users = [
{"name": "Alice", "age": 25, "active": True},
{"name": "Bob", "age": 30, "active": False},
{"name": "Charlie", "age": 35, "active": True}
]
# Get names of active users
active_names = [user["name"] for user in users if user["active"]]
print(active_names) # ['Alice', 'Charlie']
# Get ages of users over 28
older_ages = [user["age"] for user in users if user["age"] > 28]
print(older_ages) # [30, 35]
# Example 2: Data transformation
prices = [10.99, 5.50, 8.75, 12.00]
# Apply 10% discount and format as currency
discounted = [f"${price * 0.9:.2f}" for price in prices]
print(discounted) # ['$9.89', '$4.95', '$7.88', '$10.80']
# Example 3: Extracting data from strings
sentences = ["Hello world", "Python is great", "Code every day"]
word_lengths = [[len(word) for word in sentence.split()] for sentence in sentences]
print(word_lengths) # [[5, 5], [6, 2, 5], [4, 5, 3]]
Generator Expressions
Generator expressions use the same syntax as list comprehensions but with parentheses instead of square brackets. They create generators that produce values on demand, saving memory:
# List comprehension (creates entire list in memory)
squares_list = [x ** 2 for x in range(1000000)]
print(type(squares_list)) # <class 'list'>
# Generator expression (creates generator object)
squares_gen = (x ** 2 for x in range(1000000))
print(type(squares_gen)) # <class 'generator'>
# Generators are consumed as you iterate
for i, square in enumerate(squares_gen):
if i >= 5:
break
print(square) # 0, 1, 4, 9, 16
Generator expressions are perfect when you only need to iterate once and don't need the entire list in memory:
# Sum of squares without storing the list
total = sum(x ** 2 for x in range(10))
print(total) # 285
# Finding maximum without creating full list
max_value = max(x * 2 for x in range(100))
print(max_value) # 198
# Filtering and transforming in one pass
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = sum(x ** 2 for x in numbers if x % 2 == 0)
print(result) # 220 (sum of squares of even numbers)
When to Use Each
Choose list comprehensions when:
- You need to access elements multiple times
- You need the full list in memory
- You want to modify the list later
- The data set is small
Choose generator expressions when:
- You only iterate once
- Memory is a concern (large data sets)
- You're passing to functions that accept iterables (sum, max, any, all)
- You want lazy evaluation
# Good use of generator: one-time iteration
large_sum = sum(x for x in range(1000000) if x % 2 == 0)
# Good use of list comprehension: need multiple accesses
squares = [x ** 2 for x in range(10)]
print(squares[0]) # Access first element
print(squares[-1]) # Access last element
print(len(squares)) # Get length
Try It Yourself
Practice with these exercises:
-
Number Processing: Create a list comprehension that takes numbers 1-20 and returns only the squares of odd numbers.
-
String Transformation: Given a list of sentences, create a list of lists containing the length of each word in each sentence.
-
Data Filtering: From a list of dictionaries with "name" and "score" keys, create a list of names for people with scores above 80.
-
Generator Practice: Use a generator expression to calculate the sum of all numbers from 1 to 1000 that are divisible by 3 or 5, without creating a list.
-
Matrix Operations: Create a 5x5 matrix using nested comprehensions where each element is the sum of its row and column indices.
Summary
List comprehensions and generator expressions are powerful tools that make your Python code more concise and readable. List comprehensions create lists efficiently, while generator expressions create memory-efficient iterators. Both support filtering with conditions and can be nested for complex transformations.
The key is knowing when to use each. Use list comprehensions when you need the full list, and generator expressions when you're processing data once or working with large datasets. These constructs are hallmarks of Pythonic code and will make your programs more efficient and easier to read.
As you continue learning intermediate Python, you'll find comprehensions and generators used throughout the language and in popular libraries. Mastering them now will make you a more effective Python programmer.
What's Next?
In the next lesson, we'll dive deeper into dictionaries. You'll learn dictionary comprehensions, advanced dictionary methods, and how to work with nested dictionaries. These skills will help you manage complex data structures efficiently.