Python 3 — List Comprehensions & Generators
List comprehensions are one of Python's most powerful and Pythonic features. They let you build new lists from existing data in a single, readable line — replacing what would otherwise require a multi-line for loop. Generators extend this idea with lazy evaluation, computing values only when needed to save memory.
Suppose you want to create a list of squares from 1 to 10. Without comprehensions, you'd write:
# Traditional approach — 4 lines
squares = []
for n in range(1, 11):
squares.append(n ** 2)
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
This works, but it's verbose. List comprehensions collapse this into one elegant line.
The syntax is: [expression for item in iterable]
# Same result in ONE line!
squares = [n ** 2 for n in range(1, 11)]
print(squares) # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
# Another example: convert Celsius to Fahrenheit
celsius = [0, 20, 37, 100]
fahrenheit = [(c * 9/5) + 32 for c in celsius]
print(fahrenheit) # [32.0, 68.0, 98.6, 212.0]
Add an if clause to filter which items get included: [expr for item in iterable if condition]
# Get only even numbers from 1 to 20
evens = [n for n in range(1, 21) if n % 2 == 0]
print(evens) # [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
# Filter words longer than 4 characters
words = ["Python", "is", "fast", "and", "powerful"]
long_words = [w for w in words if len(w) > 4]
print(long_words) # ['Python', 'powerful']
# Get only vowels from a string
text = "Hello World"
vowels = [ch for ch in text if ch.lower() in "aeiou"]
print(vowels) # ['e', 'o', 'o']
Comprehensions work for dictionaries and sets too — just change the brackets:
# Dict comprehension: word → its length
words = ["Python", "Java", "Go", "Rust"]
word_lengths = {word: len(word) for word in words}
print(word_lengths) # {'Python': 6, 'Java': 4, 'Go': 2, 'Rust': 4}
# Set comprehension: unique first letters
fruits = ["apple", "avocado", "banana", "blueberry", "cherry"]
first_letters = {f[0] for f in fruits}
print(first_letters) # {'a', 'b', 'c'} (order may vary)
A generator is like a list comprehension but uses parentheses () instead of brackets []. Unlike lists, generators don't store all values in memory — they produce each value on-demand (lazy evaluation):
# List — all values stored in memory at once
squares_list = [n ** 2 for n in range(1, 6)]
print(squares_list) # [1, 4, 9, 16, 25]
# Generator — values computed one at a time
squares_gen = (n ** 2 for n in range(1, 6))
print(squares_gen) #
# Use next() to pull values one by one
print(next(squares_gen)) # 1
print(next(squares_gen)) # 4
# Or convert to a list when needed
print(list(squares_gen)) # [9, 16, 25] (remaining values)
Use generators when processing large datasets (like reading millions of rows from a file) because they use far less memory than building a list.
You can create a reusable generator using a regular function with the yield keyword instead of return:
def count_up(limit):
"""Yields numbers from 1 up to limit, one at a time."""
n = 1
while n <= limit:
yield n # Pauses here, sends n to caller
n += 1 # Resumes from here next time
# Using the generator
counter = count_up(5)
for num in counter:
print(num, end=" ") # 1 2 3 4 5
# Fibonacci sequence generator
def fibonacci():
a, b = 0, 1
while True: # Infinite sequence!
yield a
a, b = b, a + b
fib = fibonacci()
first_10 = [next(fib) for _ in range(10)]
print(first_10) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
| Feature | List Comprehension [] | Generator Expression () |
|---|---|---|
| Memory | Stores all values at once | Computes one value at a time |
| Speed | Faster for small data | Better for large/infinite data |
| Reusable | Yes — iterate many times | No — exhausted after one pass |
| Use case | Small lists, need random access | Large files, streams, pipelines |
Write a list comprehension that generates all prime numbers between 2 and 50. A prime number is only divisible by 1 and itself. Hint: Use a nested comprehension to check if any number from 2 to n-1 divides evenly.