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