1️⃣ What is a Generator
- A function that yields values lazily
- Uses
yield - Remembers state
- Does not keep all data in memory
def gen():
yield 1
yield 2
yield 3
g = gen()
print(g) # <generator object gen at 0x...>
print(list(g)) # [1, 2, 3]
print(list(g)) # [] (exhausted)2️⃣ Lazy Evaluation Concept
Generator produces values only when asked
def squares(n):
for i in range(n):
print(f"computing {i}^2")
yield i * i
sq = squares(3)
print(next(sq)) # computing 0^2 -> 0
print(next(sq)) # computing 1^2 -> 1
print(next(sq)) # computing 2^2 -> 4
# next(sq) # StopIteration3️⃣ Iterating using for on fun that yield value
def count(n):
i = 1
while i <= n:
yield i
i += 1
for x in count(5):
print(x)
# 1
# 2
# 3
# 4
# 54️⃣ Generator Expression (Lazy comprehension)
nums = (x for x in range(4))
print(next(nums)) # 0
print(next(nums)) # 1
print(nums) # <generator object <genexpr> at 0x...>
print(list(nums)) # [2, 3]
print(list(nums)) # []
print(next(nums)) # Error5️⃣ Memory Efficiency Proof
import sys
l = [x for x in range(10000)]
g = (x for x in range(10000))
print(sys.getsizeof(l)) # 85176 bytes
print(sys.getsizeof(g)) # 192 bytes6️⃣ Infinite Generators
def infinite():
i = 0
while True:
yield i
i += 1
inf = infinite()
print(next(inf)) # 0
print(next(inf)) # 1
print(next(inf)) # 2
print(next(inf)) # 37️⃣ Generator Pipeline (chaining)
def nums():
for i in range(6):
yield i
def evens(stream):
for x in stream:
if x % 2 == 0:
yield x
def mul2(stream):
for x in stream:
yield x * 2
pipe = mul2(evens(nums()))
print(list(pipe)) # [0, 4, 8]
# OR
print(next(pipe)) # 0
print(next(pipe)) # 4
Breakdown:
- nums →
[0,1,2,3,4,5] - evens →
[0,2,4] - mul2 →
[0,4,8]
nums() → 0 → evens keeps → mul2 → 0*2 → 0
nums() → 1 → evens drops
nums() → 2 → evens keeps → mul2 → 2*2 → 4
▶️ First call: next(pipe)
mul2()asks its source for a value (next(evens(...)))evens()asks its source for a value (next(nums()))nums()runs until firstyield
nums() yields 0 # 0
evens()checks0 % 2 == 0→ True, so it yields0mul2()takes0and yields0 * 2
next(pipe) prints 0 # 0
▶️ Second call: next(pipe)
mul2()again asksevens()for nextevens()asksnums()for nextnums()resumes from where it paused and yields1
nums() yields 1 # 1
evens()checks1 % 2 == 0→ False, so it skipsevens()asksnums()againnums()resumes and yields2
nums() yields 2 # 2
evens()checks2 % 2 == 0→ True, so yields2mul2()takes2and yields2 * 2
next(pipe) prints 4 # 4
So the real execution looks like this:
nums() → 0 → evens keeps → mul2 → 0*2 → 0
nums() → 1 → evens drops
nums() → 2 → evens keeps → mul2 → 2*2 → 4
8️⃣ yield remembers state
def step():
x = 10
yield x
x += 5
yield x
x += 5
yield x
s = step()
print(next(s)) # 10
print(next(s)) # 15
print(next(s)) # 209️⃣ Two-way Generator using send()
def talk():
msg = yield "Start"
yield f"You said {msg}"
msg2 = yield "Ask again"
yield f"Second msg {msg2}"
t = talk()
print(next(t)) # Start
print(next(t)) # Ask again ❌ Wrong, because we skipped send
# Correct usage ↓
t = talk()
print(next(t)) # Start
print(t.send("Hi")) # You said Hi
print(t.send(123)) # Second msg 123What really happens one by one
-
next(t)- Generator starts
- Hits
yield "Start" - Returns
"Start" - You print it →
Start
-
t.send("Hi")"Hi"goes intomsg- Generator resumes
- Hits
yield f"You said {msg}"→"You said Hi" - Returns
"You said Hi" - You print it →
You said Hi
-
t.send(123)123goes intomsg2- Generator resumes
- Next
yieldit reaches isyield "Ask again" - Returns
"Ask again" - You print it →
Ask again
-
After that, the generator is paused at
yield f"Second msg {msg2}"if you never called next() again, so it won’t print "Second msg 123"
- If you call one more:
print(next(t)) # Second msg 123
Note
send(value)only works after the generator has started usingnext()
🔚 Core Rules Recap
1. Generator is exhausted after one full consumption
2. Uses yield and pauses execution
3. Remembers local variables between yields
4. Does not store full data in memory
5. send() needs next() first to prime