1.2.36 · D5 · HinglishIntroduction to Programming (Python)

Question bankGenerator expressions — memory efficiency

1,629 words7 min read↑ Read in English

1.2.36 · D5 · Coding › Introduction to Programming (Python) › Generator expressions — memory efficiency

Yeh bank generator expressions ke conceptual traps drill karta hai — arithmetic nahi. Har item ek misconception ya ek boundary case ko target karta hai. Related ideas: Lazy evaluation, Iterators and the iterator protocol, List comprehensions, Big-O space complexity.

Shuru karne se pehle, ek shared vocabulary reminder taaki koi bhi word use hone se pehle clearly define ho jaye:


True ya false — justify karo

Ek generator expression apna loop body likhte hi run kar leta hai.
False. (x*x for x in ...) likhne se sirf ek generator object banta hai; loop body baad mein chalta hai, ek step per value pull hone par — yahi Lazy evaluation hai.
(x for x in range(3)) aur [x for x in range(3)] same amount of memory use karte hain.
False. List teen items materialise karta hai (); generator sirf apni state aur current item store karta hai ().
Ek generator expression ko jitni baar chaaho iterate kar sakte ho.
False. Yeh single-use hota hai: ek full pass ke baad yeh exhausted ho jaata hai aur baad ki har iteration mein kuch yield nahi karta.
Comprehension ke around parentheses lagane se hamesha generator banta hai.
Thoda False — ek bare (expr) sirf grouping hai; tumhe andar for chahiye, e.g. (expr for item in it). (5) number 5 hai, generator nahi.
if filter wala generator phir bhi source ke har element ko visit karta hai.
True. Filter decide karta hai kya yield hoga, lekin underlying iterable phir bhi item by item chali jaati hai jab tum use consume karte ho.
Generator expression par len() call karne se pata chalta hai kitne items produce honge.
False. Generators ki koi length nahi hoti — Python ko count karne ke liye poora run karna padta, jo laziness ko defeat karta, isliye len(g) TypeError raise karta hai.
Agar tum kabhi generator iterate nahi karte, to uska loop body bilkul execute nahi hota.
True. Koi consumption nahi matlab koi computation nahi — jo generator tum banate aur drop kar dete ho woh essentially kuch nahi karta.
list(g) se generator ko list mein convert karna memory savings wapas deta hai.
False. list(g) sab kuch materialise kar deta hai, isliye tum wapas memory par aa jaate ho — tumne laziness phenk di.

Error dhundho

g = (x*x for x in range(5))
print(g[0])

::: Error — generators subscriptable nahi hote. g[0] TypeError raise karta hai; tumhe next(g) ya iterate use karna hoga, kyunki koi stored element hai hi nahi jise index kar sako.

g = (x for x in range(3))
a = list(g)
b = list(g)   # expecting [0,1,2] again

::: b hai [], naa ki [0,1,2]. Pehle list(g) ne generator exhaust kar diya, isliye doosra pass kuch nahi dekhta — yeh ek classic single-pass trap hai.

total = sum(x for x in range(10) if x % 2)
avg = total / len(x for x in range(10) if x % 2)

::: Generator par len(...) TypeError raise karta hai, aur conceptually bhi yeh galat hai: generator ki koi length nahi hoti. Agar tumhe sum aur count dono chahiye to pehle values ko list mein store karo.

def make():
    return (line for line in open("data.txt"))
gen = make()
# ... baad mein, file kahin aur already close ho gayi ...
first = next(gen)

::: Generator lazy hai, isliye file tab read hoti hai jab consume ki jaaye, naa ki jab make() return hua. Agar next call karne se pehle file close ho gayi, toh "read of closed file" error aata hai — laziness kab side-effects hote hain, woh move kar deta hai.

pairs = (a, b for a in range(2) for b in range(2))

::: Syntax error — (a, b for ...) ambiguous hai. Tuples yield karne ke liye expression ko parenthesise karna padega: ((a, b) for a in range(2) for b in range(2)).

nums = (x for x in range(3))
if nums:
    print("has items")

::: if nums hamesha truthy hota hai — ek generator object truthy hota hai chahe empty ho ya exhausted. Truthiness test karna tumhe nahi batata ki uske paas values hain ya nahi; tumhe next() try karna padega.


Why questions

Generator ki memory kyun hoti hai chahe woh kitni bhi values yield kare?
Kyunki woh sirf fixed state (ek frame aur ek position pointer) plus ek current item store karta hai; "ek waqt mein stored number" 1 hai, isliye per-element term kabhi accumulate nahi hoti — Big-O space complexity dekho.
Lazy file filter par next(error_lines) sirf file ka kuch hissa padhke kyun ruk sakta hai?
Laziness har line ko sirf demand par compute karta hai, isliye jaise hi pehla match milta hai, next return karta hai aur koi aur line nahi padhi jaati.
sum(x*x for x in range(10**6)) memory mein list version se better kyun hai lekin speed mein zaroori nahi?
Memory jeet jaati hai kyunki koi million-element list allocate nahi hoti; speed similar hoti hai kyunki wahi million squares phir bhi ek ek karke compute hote hain — laziness space bachati hai, work nahi.
sum(x*x for x in data) mein inner parentheses kyun drop kar sakte ho?
Jab generator expression kisi function call ka sole argument hota hai, Python call ke apne parentheses ko generator ke parentheses ki tarah serve karne deta hai, isliye sum((...)) ko sum(...) likha ja sakta hai.
if cond se filter karne wala generator kabhi kabhi "slow to start" kyun lagta hai?
Use source se tab tak pull karte rehna padta hai jab tak koi value filter pass nahi kar leti; agar matches rare hain, to next yield karne se pehle bahut saare rejected items walk karta hai — kaam defer hota hai, skip nahi.
Generator ko variable mein store karna uski values ko consume hone se kyun protect nahi karta?
Variable usi ek stateful object ko hold karta hai; koi bhi consumer jo use aage badhaye (for, sum, ya stray list) permanently shared position aage move kar deta hai.

Edge cases

list(x for x in range(0)) kya produce karta hai?
[] — empty range kuch yield nahi karta, isliye generator born hote hi exhausted hota hai; yeh zero-length boundary hai, aur yeh ek valid, error-free result hai.
g puri tarah exhausted hone ke baad next(g) kya karta hai?
Woh StopIteration raise karta hai — wahi signal jo for loop silently catch karta hai yeh jaanne ke liye ki use ruk jaana chahiye.
Kya ek generator expression infinite ho sakta hai, aur kya woh safe hai?
Haan, e.g. (x for x in itertools.count()); yeh safe hai sirf tab agar tum use kabhi puri tarah materialise nahi kartelist() ya sum() use karna hamesha ke liye loop karega, lekin next() kuch baar karna theek hai.
Agar source iterable generator mid-iteration mein change ho jaye to kya hoga?
Kyunki yeh lazily read karta hai, baad ke next calls source ki current state dekhte hain; iteration ke dauran source ko mutate karna items skip ya repeat kar sakta hai aur yeh ek common bug hai.
Kya aisa filter jo kuch match nahi karta, jaise (x for x in range(5) if x > 100), error raise karta hai?
Nahi — woh simply koi value yield nahi karta aur empty generator ki tarah behave karta hai; list(...) [] deta hai aur next(...) StopIteration raise karta hai.
Kya (x for x in []) aur (x for x in range(0)) behaviour mein alag hain?
Nahi — dono ek empty source wrap karte hain aur kuch yield nahi karte; source type alag hai lekin exhausted-from-birth behaviour identical hai.

Recall Ek line ka summary jo saath le jao

Ek generator expression ek lazy, single-pass, -memory recipe hai: yeh demand par compute karta hai, apne source ko ek baar walk karta hai, ek waqt mein ek item hold karta hai, aur ise index nahi kiya ja sakta, len se measure nahi kiya ja sakta, ya replay nahi kiya ja sakta.

Connections

  • Lazy evaluation — kyun "compute on demand" yahan har trap ka base hai
  • Iterators and the iterator protocol — single-pass / StopIteration mechanics
  • List comprehensions — eager [] cousin jo reuse aur index ho sakta hai
  • Big-O space complexity vs distinction
  • Generator functions and yielddef/yield sibling with the same laziness