Generators
Generator function normal function jaisa hi dikhta hai, lekin return ki jagah yield use karta hai — call karne par, ye turant poora execute nahi hota, balki ek "generator object" return karta hai jo values ko LAZILY (on-demand) produce karta hai.
Fayda: memory efficiency — agar tumhe 10 million numbers process karne hain, generator ek time par ek hi number memory mein rakhta hai, poori list ek saath nahi (jo crash bhi kar sakta hai bade datasets ke liye).
def count_up_to(n):
i = 1
while i <= n:
yield i # value do, lekin function "pause" ho jaata hai yahin
i += 1
for num in count_up_to(5):
print(num) # 1 2 3 4 5 — ek time par ek generate hota hai
# Memory comparison:
def squares_list(n): # poori list memory mein
return [x**2 for x in range(n)]
def squares_gen(n): # ek time par ek value
for x in range(n):
yield x ** 2
# squares_list(10_000_000) bahut memory legi
# squares_gen(10_000_000) almost koi memory nahi legi turant- yield = value do aur pause karo, return jaisa nahi (poora khatam nahi hota)
- Lazy evaluation — memory-efficient bade/infinite sequences ke liye
- for loop generator ko automatically consume karta hai
Generator function (yield wala) aur generator expression ((x for x in ...)) dono lazy hain, same underlying concept — bas syntax alag hai. Expression simple cases ke liye, function complex logic ke liye.
# Generator function:
def squares(n):
for x in range(n):
yield x ** 2
# Generator expression — same result, simpler cases ke liye:
squares_expr = (x ** 2 for x in range(5))
for val in squares_expr:
print(val) # 0 1 4 9 16Generators sirf values produce nahi karte, values RECEIVE bhi kar sakte hain generator.send(value) se — advanced pattern, coroutines/pipelines banane mein use hota hai.
def echo():
while True:
received = yield
print(f"Received: {received}")
gen = echo()
next(gen) # generator ko "start" karo (pehle yield tak)
gen.send("Hello") # "Received: Hello"
gen.send("World") # "Received: World"