Generator expressions — memory efficiency
WHAT is a generator expression?
list_comp = [x*x for x in range(1_000_000)] # builds 1,000,000 ints NOW
gen_expr = (x*x for x in range(1_000_000)) # builds NOTHING yetlist_compis a reallistholding a million numbers.gen_expris a tinygeneratorobject that remembers how to produce them.
WHY does this save memory?
So the memory cost is:
| Object | Memory used | Grows with ? |
|---|---|---|
| List of items | stores all items | Yes — |
| Generator | stores 1 item + the recipe | No — |
HOW do you actually use it?
You consume it by iterating — for, sum(), next(), any(), etc. Each consumes values once.
# next() pulls ONE value
g = (x*x for x in range(5))
next(g) # 0
next(g) # 1 <- state advanced, 0 is gone
# A function that takes an iterable consumes the whole generator
total = sum(x*x for x in range(1_000_000)) # parentheses optional as sole arg
Worked Examples
Recall Feynman: explain to a 12-year-old
Imagine you want to eat 100 sandwiches. List way: make all 100 first, pile them on the table (huge mess, lots of space). Generator way: the kitchen makes the next sandwich only when you finish the last one. You're never holding more than one — so you need almost no table space, no matter if it's 100 or a million sandwiches. The catch: once you've eaten them all, they're gone — you can't go back and eat the same ones again.
Active Recall
What punctuation distinguishes a generator expression from a list comprehension?
() instead of square brackets [].What is the memory complexity of a generator expression vs a list comprehension over n items?
Why does (x*x for x in range(n)) use almost no memory?
What happens when you iterate a generator a second time?
When summing 1,000,000 squares, why prefer sum(x*x for x in range(10**6)) over a list comprehension?
Can you index a generator like g[0]?
If a generator expression is the sole argument to a function, what can you drop?
sum(x*x for x in data).Connections
- List comprehensions — same syntax, eager evaluation
- Iterators and the iterator protocol —
__iter__/__next__underpin generators - Generator functions and yield — the
def/yieldcousin of generator expressions - Lazy evaluation — the general principle of "compute when needed"
- Big-O space complexity — why O(1) vs O(n) matters
- Memory management in Python — heap allocation of lists
Concept Map
Hinglish (regional understanding)
Intuition Hinglish mein samjho
Dekho yaar, generator expression ka funda simple hai: list comprehension [x for x in ...] saari values ek saath bana ke memory mein rakh deta hai, jabki generator expression (x for x in ...) — sirf brackets badalne se — values ko on demand, ek-ek karke deta hai. Matlab agar tumhe ek lakh squares chahiye, list poore ek lakh numbers memory mein store karegi (heavy!), par generator sirf "recipe" rakhta hai aur jab tum for ya sum se maangte ho tabhi next value banata hai. Isse memory O(n) se O(1) ho jaati hai — chahe 10 items ho ya 1 crore, generator ki memory almost constant rehti hai.
Yeh kyun important hai? Bade data ke saath kaam karte time, jaise badi files padhna ya millions of records process karna, list approach se RAM full ho sakti hai aur program crash. Generator se tum lazy chalte ho — jitna chahiye utna hi banao. Isliye sum(x*x for x in range(10**6)) likhna best practice hai, beech mein faltu ki badi list mat banao.
Ek important trap yaad rakhna: generator ek hi baar chalta hai. Ek baar list(g) ya for se consume kar liya, to dobara woh khaali ho jaata hai — second baar kuch nahi dega. Agar dobara chahiye to phir se generator banao, ya seedha list use karo. Mnemonic: PARENS = PATIENCE — round brackets Python ko patient banate hain (ek-ek value), square brackets greedy (sab ek saath).