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. Can be infinite
# 6. send() needs next() first to prime