Pythonic Patterns
Generator Expressions
Inline Generators
You want sum(x**2 for x in range(1000000)). Creating a list of a million
squares would waste memory. Generator expressions use parentheses instead of
brackets and create values on demand - perfect for aggregation functions.
Basic syntax
Use parentheses instead of brackets.
# Basic Generator Expression
print("=== Generator Expression vs List Comprehension ===\n")
# List comprehension: square brackets
limit =
list_comp = [x ** 2 for x in range(limit)]
print(f"List comprehension: {list_comp}")
print(f"Type: {type(list_comp)}")
# Generator expression: parentheses
gen_exp = (x ** 2 for x in range(limit))
print(f"\nGenerator expression: {gen_exp}")
print(f"Type: {type(gen_exp)}")
print("\n=== Getting Values ===")
# List is ready to use
print(f"List[0]: {list_comp[0]}")
print(f"List length: {len(list_comp)}")
# Generator must be iterated
print("\nGenerator values:")
for val in gen_exp:
print(f" {val}")
print("\n=== One-Time Use ===")
gen = (x for x in range(3))
print("First iteration:")
print(f" {list(gen)}") # [0, 1, 2]
print("Second iteration:")
print(f" {list(gen)}") # [] - Empty! Already exhausted
print("\n=== Syntax Comparison ===")
print("""
List: [expression for item in iterable]
Generator: (expression for item in iterable)
^ ^
Parentheses instead of brackets!
""")
# Both support conditions
list_even = [x for x in range(10) if x % 2 == 0]
gen_even = (x for x in range(10) if x % 2 == 0)
print(f"List (evens): {list_even}")
print(f"Generator (evens): {list(gen_even)}")
# Basic Generator Expression
print("=== Generator Expression vs List Comprehension ===\n")
# List comprehension: square brackets
limit =
list_comp = [x ** 2 for x in range(limit)]
print(f"List comprehension: {list_comp}")
print(f"Type: {type(list_comp)}")
# Generator expression: parentheses
gen_exp = (x ** 2 for x in range(limit))
print(f"\nGenerator expression: {gen_exp}")
print(f"Type: {type(gen_exp)}")
print("\n=== Getting Values ===")
# List is ready to use
print(f"List[0]: {list_comp[0]}")
print(f"List length: {len(list_comp)}")
# Generator must be iterated
print("\nGenerator values:")
for val in gen_exp:
print(f" {val}")
print("\n=== One-Time Use ===")
gen = (x for x in range(3))
print("First iteration:")
print(f" {list(gen)}") # [0, 1, 2]
print("Second iteration:")
print(f" {list(gen)}") # [] - Empty! Already exhausted
print("\n=== Syntax Comparison ===")
print("""
List: [expression for item in iterable]
Generator: (expression for item in iterable)
^ ^
Parentheses instead of brackets!
""")
# Both support conditions
list_even = [x for x in range(10) if x % 2 == 0]
gen_even = (x for x in range(10) if x % 2 == 0)
print(f"List (evens): {list_even}")
print(f"Generator (evens): {list(gen_even)}")
# Basic Generator Expression
print("=== Generator Expression vs List Comprehension ===\n")
# List comprehension: square brackets
limit =
list_comp = [x ** 2 for x in range(limit)]
print(f"List comprehension: {list_comp}")
print(f"Type: {type(list_comp)}")
# Generator expression: parentheses
gen_exp = (x ** 2 for x in range(limit))
print(f"\nGenerator expression: {gen_exp}")
print(f"Type: {type(gen_exp)}")
print("\n=== Getting Values ===")
# List is ready to use
print(f"List[0]: {list_comp[0]}")
print(f"List length: {len(list_comp)}")
# Generator must be iterated
print("\nGenerator values:")
for val in gen_exp:
print(f" {val}")
print("\n=== One-Time Use ===")
gen = (x for x in range(3))
print("First iteration:")
print(f" {list(gen)}") # [0, 1, 2]
print("Second iteration:")
print(f" {list(gen)}") # [] - Empty! Already exhausted
print("\n=== Syntax Comparison ===")
print("""
List: [expression for item in iterable]
Generator: (expression for item in iterable)
^ ^
Parentheses instead of brackets!
""")
# Both support conditions
list_even = [x for x in range(10) if x % 2 == 0]
gen_even = (x for x in range(10) if x % 2 == 0)
print(f"List (evens): {list_even}")
print(f"Generator (evens): {list(gen_even)}")
(expression for item in iterable) - same as list comprehension but lazy.
Memory efficiency
Compare memory: list comprehension vs generator expression.
# Memory Efficiency
import sys
print("=== Memory Comparison ===\n")
n = 10000 # Ten thousand numbers
# List comprehension
list_squares = [x ** 2 for x in range(n)]
list_size = sys.getsizeof(list_squares)
# Generator expression
gen_squares = (x ** 2 for x in range(n))
gen_size = sys.getsizeof(gen_squares)
print(f"Squares of 0 to {n-1}:")
print(f" List: {list_size:,} bytes")
print(f" Generator: {gen_size:,} bytes")
print(f" Ratio: List is {list_size / gen_size:.0f}x larger!")
print("\n=== Why The Difference? ===")
print("""
List comprehension:
[0, 1, 4, 9, 16, 25, ..., 99980001]
→ Stores ALL 10,000 squared values
→ Each integer takes memory
→ Plus list structure overhead
Generator expression:
→ Stores only the "recipe" to compute values
→ Computes each value when requested
→ Memory constant regardless of size
""")
print("=== Scaling Comparison ===\n")
sizes = [100, 1000, 10000, 100000]
for size in sizes:
list_obj = [x for x in range(size)]
gen_obj = (x for x in range(size))
list_bytes = sys.getsizeof(list_obj)
gen_bytes = sys.getsizeof(gen_obj)
print(f"Size {size:>6,}: List {list_bytes:>8,} bytes, Gen {gen_bytes:>4} bytes")
print("\nNotice: Generator size stays constant!")
print("\n=== When Memory Matters ===")
# Processing large data
def process_large_dataset():
# BAD: Creates huge list in memory
# results = [expensive_operation(x) for x in huge_dataset]
# GOOD: Processes one at a time
# results = (expensive_operation(x) for x in huge_dataset)
pass
print("""
Use generator expressions when:
✓ Processing large datasets
✓ You only iterate once
✓ Memory is constrained
✓ Early termination is possible
Use list comprehensions when:
✓ You need random access (result[5])
✓ You need to iterate multiple times
✓ You need len()
✓ Dataset is small
""")
List stores all values. Generator expression produces one at a time.
With built-in functions
Generator expressions work great with sum, any, all, max, min.
# Generator Expressions with Built-in Functions
print("=== sum() with Generator Expression ===\n")
# Sum of squares 1-100
# List way (creates intermediate list)
list_sum = sum([x ** 2 for x in range(1, 101)])
print(f"List way: sum([...]) = {list_sum}")
# Generator way (no intermediate list!)
gen_sum = sum(x ** 2 for x in range(1, 101))
print(f"Generator way: sum(...) = {gen_sum}")
print("\nNote: Can omit outer () when only argument!")
print("\n=== any() and all() ===")
numbers = [2, 4, 6, 7, 8, 10]
print(f"Numbers: {numbers}")
# any(): True if at least one True
has_odd = any(n % 2 != 0 for n in numbers)
print(f"any(n % 2 != 0): {has_odd}") # True (7 is odd)
# all(): True if all are True
all_positive = all(n > 0 for n in numbers)
print(f"all(n > 0): {all_positive}") # True
all_even = all(n % 2 == 0 for n in numbers)
print(f"all(n % 2 == 0): {all_even}") # False (7 is odd)
print("\n=== Short-Circuit Evaluation ===")
def check(n):
print(f" Checking {n}...")
return n > 5
data = [1, 2, 3, 6, 7, 8]
print(f"Data: {data}")
print("\nany(n > 5 for n in data):")
result = any(check(n) for n in data)
print(f"Result: {result}")
print("Stopped at 6! Didn't check 7 or 8.")
print("\n=== max() and min() ===")
words = ["apple", "pie", "extraordinary", "hi"]
print(f"Words: {words}")
longest = max(len(w) for w in words)
shortest = min(len(w) for w in words)
print(f"Longest word length: {longest}")
print(f"Shortest word length: {shortest}")
# Finding the actual word
longest_word = max(words, key=len)
print(f"Longest word: '{longest_word}'")
print("\n=== join() with Generator ===")
numbers = [1, 2, 3, 4, 5]
# Convert to strings and join
result = ', '.join(str(n) for n in numbers)
print(f"Joined: {result}")
sum(x**2 for x in items) - no intermediate list needed.
Iterate over generators
Use in for loops or convert to list when needed.
# Iterating Over Generator Expressions
print("=== for Loop ===\n")
# Basic iteration
gen = (x ** 2 for x in range(5))
print("Squares (for loop):")
for square in gen:
print(f" {square}")
print("\n=== Converting to Collections ===")
# To list
gen1 = (x * 2 for x in range(5))
as_list = list(gen1)
print(f"list(): {as_list}")
# To tuple
gen2 = (x * 2 for x in range(5))
as_tuple = tuple(gen2)
print(f"tuple(): {as_tuple}")
# To set
gen3 = (x % 3 for x in range(10))
as_set = set(gen3)
print(f"set(x % 3 for x in range(10)): {as_set}")
print("\n=== Manual Iteration with next() ===")
gen = (x ** 2 for x in range(3))
print("Using next():")
print(f" next(): {next(gen)}") # 0
print(f" next(): {next(gen)}") # 1
print(f" next(): {next(gen)}") # 4
print("\nExhausted - next() would raise StopIteration")
print("\n=== Unpacking ===")
# Unpack into variables
gen = (x for x in range(3))
a, b, c = gen
print(f"a, b, c = generator: {a}, {b}, {c}")
# Unpack with *
gen = (x ** 2 for x in range(5))
first, *rest = gen
print(f"first, *rest: first={first}, rest={rest}")
print("\n=== enumerate() with Generator ===")
words = ["apple", "banana", "cherry"]
gen = (w.upper() for w in words)
print("Enumerated generator:")
for i, word in enumerate(gen):
print(f" {i}: {word}")
print("\n=== zip() with Generators ===")
gen1 = (x for x in range(3))
gen2 = (x ** 2 for x in range(3))
print("Zipped generators:")
for a, b in zip(gen1, gen2):
print(f" {a} -> {b}")
Generators are single-use. Convert to list if you need to iterate multiple times.
Conditions in generator expressions
Filter values with if.
# Conditions in Generator Expressions
print("=== Filtering with if ===\n")
numbers = list(range(1, 11))
print(f"Numbers: {numbers}")
# Filter: only even numbers
evens = (n for n in numbers if n % 2 == 0)
print(f"Evens: {list(evens)}")
# Filter: only values > 5
big = (n for n in numbers if n > 5)
print(f"Greater than 5: {list(big)}")
print("\n=== Multiple Conditions ===")
# AND condition (both must be true)
even_and_big = (n for n in numbers if n % 2 == 0 and n > 5)
print(f"Even AND > 5: {list(even_and_big)}")
# Multiple if clauses (same as AND)
even_and_big2 = (n for n in numbers if n % 2 == 0 if n > 5)
print(f"Using multiple if: {list(even_and_big2)}")
print("\n=== Conditional Expression (if-else) ===")
# Transform based on condition
labels = ("even" if n % 2 == 0 else "odd" for n in range(1, 6))
print(f"Labels for 1-5: {list(labels)}")
# Different position!
print("""
Filter (if only): (x for x in items if condition)
^^^^^^^^^^^^
At the END
Transform (if-else): (a if cond else b for x in items)
^^^^^^^^^^^^^^^^
At the BEGINNING
""")
print("=== Combining Filter and Transform ===")
data = list(range(-5, 6))
print(f"Data: {data}")
# Filter positives, then double them
result = (n * 2 for n in data if n > 0)
print(f"Positive numbers doubled: {list(result)}")
# Transform (absolute value) without filter
result2 = (abs(n) for n in data)
print(f"Absolute values: {list(result2)}")
# Conditional transform: double positive, negate negative
result3 = (n * 2 if n > 0 else -n for n in data if n != 0)
print(f"Transform (skip 0): {list(result3)}")
print("\n=== String Filtering ===")
words = ["apple", "ant", "Banana", "avocado", "Berry"]
print(f"Words: {words}")
# Lowercase words starting with 'a'
a_words = (w for w in words if w.lower().startswith('a'))
print(f"Start with 'a': {list(a_words)}")
# First letter, only long words
initials = (w[0] for w in words if len(w) > 5)
print(f"Initials of long words: {list(initials)}")
(x for x in items if x > 0) - filter while generating.
Exercise: practical.py
Real-world generator expression patterns