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)       # StopIteration

3️⃣ 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
# 5

4️⃣ 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)) # Error

5️⃣ 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 bytes

6️⃣ 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)) # 3

7️⃣ 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*20
nums() → 1 → evens drops
nums() → 2 → evens keeps → mul2 → 2*24
 

▶️ First call: next(pipe)

  1. mul2() asks its source for a value (next(evens(...)))
  2. evens() asks its source for a value (next(nums()))
  3. nums() runs until first yield
 
nums() yields 0 # 0
 
  1. evens() checks 0 % 2 == 0 → True, so it yields 0
  2. mul2() takes 0 and yields 0 * 2
 
next(pipe) prints 0 # 0
 

▶️ Second call: next(pipe)

  1. mul2() again asks evens() for next
  2. evens() asks nums() for next
  3. nums() resumes from where it paused and yields 1
 
nums() yields 1 # 1
 
  1. evens() checks 1 % 2 == 0 → False, so it skips
  2. evens() asks nums() again
  3. nums() resumes and yields 2
 
nums() yields 2 # 2
 
  1. evens() checks 2 % 2 == 0 → True, so yields 2
  2. mul2() takes 2 and yields 2 * 2
 
next(pipe) prints 4 # 4
 

So the real execution looks like this:

 
nums() → 0 → evens keeps → mul2 → 0*20
nums() → 1 → evens drops
nums() → 2 → evens keeps → mul2 → 2*24
 

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)) # 20

9️⃣ 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 123

What really happens one by one

  1. next(t)

    • Generator starts
    • Hits yield "Start"
    • Returns "Start"
    • You print it → Start
  2. t.send("Hi")

    • "Hi" goes into msg
    • Generator resumes
    • Hits yield f"You said {msg}""You said Hi"
    • Returns "You said Hi"
    • You print it → You said Hi
  3. t.send(123)

    • 123 goes into msg2
    • Generator resumes
    • Next yield it reaches is yield "Ask again"
    • Returns "Ask again"
    • You print it → Ask again
  4. 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"

  1. If you call one more:
 
print(next(t)) # Second msg 123
 

Note

send(value) only works after the generator has started using next()


🔚 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