PyPython · Lesson 11 of 14

Comprehensions & Generators

List comprehensions are Pythonic. They are also extremely easy to abuse until you have written a one-liner that takes 10 minutes to understand. Use them for simple transformations; reach for a loop when it gets complicated.

Python
# List comprehension — [expression for item in iterable if condition]
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

squares = [n**2 for n in numbers]
print(squares)  # [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

evens = [n for n in numbers if n % 2 == 0]
print(evens)  # [2, 4, 6, 8, 10]

even_squares = [n**2 for n in numbers if n % 2 == 0]
print(even_squares)  # [4, 16, 36, 64, 100]

# Nested comprehension (flatten a 2D list)
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [x for row in matrix for x in row]
print(flat)  # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Dict comprehension
words = ["hello", "world", "python"]
lengths = {word: len(word) for word in words}
print(lengths)  # {'hello': 5, 'world': 5, 'python': 6}

# Set comprehension
dupes = [1, 2, 2, 3, 3, 3, 4]
unique = {x**2 for x in dupes}
print(unique)  # {1, 4, 9, 16} (order not guaranteed)

# Conditional expression (ternary)
labels = ["even" if n % 2 == 0 else "odd" for n in range(1, 6)]
print(labels)  # ['odd', 'even', 'odd', 'even', 'odd']
Python
# Generator expressions — lazy, memory-efficient
# Use () instead of []
gen = (n**2 for n in range(1_000_000))  # creates no list yet
print(next(gen))  # 0
print(next(gen))  # 1
print(sum(gen))   # computes on the fly

# Generator function — yield instead of return
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print([next(fib) for _ in range(10)])  # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# Generator with yield from
def chain(*iterables):
    for it in iterables:
        yield from it

print(list(chain([1, 2], [3, 4], [5])))  # [1, 2, 3, 4, 5]

# itertools — powerful combinator utilities
import itertools

print(list(itertools.islice(fibonacci(), 8)))     # first 8 Fibonacci numbers
print(list(itertools.product([0,1], repeat=3)))   # all 3-bit binary combos
print(list(itertools.combinations("ABCD", 2)))    # C(4,2) = 6 pairs
pairs = list(itertools.zip_longest([1,2,3], [4,5], fillvalue=0))
print(pairs)  # [(1, 4), (2, 5), (3, 0)]
◆ Note
Generator expressions use O(1) memory regardless of input size. Prefer them over list comprehensions when you only need to iterate once and do not need random access. sum(x**2 for x in range(1_000_000)) is faster and uses less memory than sum([x**2 for x in range(1_000_000)]).