⚡ Python Generators — Memory-Efficient Iterators

Introduction 🌟

A generator in Python is a special type of function that returns values **one at a time** using the yield keyword instead of return. Generators are extremely memory-efficient because they produce items **on demand**.

Note

💡 Generators are perfect for large data, streaming, or infinite sequences.

1. What Makes Generators Special? 🧠

  • Use yield instead of return.
  • Do NOT store all values in memory.
  • Execution pauses and resumes on every yield.
  • Automatically create an iterator.

2. Basic Generator Function 🧱

basic_generator.py

def count_up_to(n):
    i = 1
    while i <= n:
        yield i
        i += 1

for num in count_up_to(5):
    print(num)

✔️ Values print one at a time, without storing all 5 numbers at once.

3. Generator vs Normal Function ⚔️

Normal FunctionGenerator Function
Uses returnUses yield
Returns onceReturns multiple values lazily
Stores all dataProduces data one-by-one
Not memory efficientVery memory efficient

4. Using next() to Manually Control Generators 🎮

next_generator.py

gen = count_up_to(3)

print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
# next(gen)StopIteration

Note

✔️ After the last yield, the generator raises StopIteration.

5. Generator Expressions ⚡ (Short Syntax)

Similar to list comprehensions but with **( ) parentheses**.

generator_expression.py

squares = (x * x for x in range(1, 6))

for s in squares:
    print(s)

Note

✔️ Does NOT create a list — generates values lazily.

6. Infinite Generators ♾️

infinite_generator.py

def infinite_counter():
    n = 1
    while True:
        yield n
        n += 1

gen = infinite_counter()
print(next(gen))
print(next(gen))
print(next(gen))

Note

⚠️ Be careful — infinite generators run forever unless controlled.

7. Generator for Large Data Files 📁

read_file_generator.py

def read_lines(filename):
    with open(filename) as file:
        for line in file:
            yield line.strip()

for line in read_lines("data.txt"):
    print(line)

✔️ Efficient for reading large files line by line.

8. Chaining Generators 🔗

chain_generators.py

def numbers():
    for i in range(1, 6):
        yield i

def squares():
    for n in numbers():
        yield n * n

print(list(squares()))

✔️ Output: [1, 4, 9, 16, 25]

9. Sending Data to Generators (send()) 📤

send_generator.py

def greeter():
    name = yield "Enter your name:"
    yield f"Hello {name}"

gen = greeter()
print(next(gen))          # Start generator
print(gen.send("Sathish"))

Note

✔️ send() injects values into a generator.

10. Using yield from (Delegating Generators) 🔁

yield_from.py

def gen1():
    yield from [1, 2, 3]

def gen2():
    yield from gen1()
    yield 4

print(list(gen2()))

✔️ Output: [1, 2, 3, 4]

11. Catching StopIteration 🚨

stop_iteration.py

def simple():
    yield 1
    yield 2

gen = simple()

try:
    while True:
        print(next(gen))
except StopIteration:
    print("Done")

12. Real-World Examples 🌍

Streaming Sensor Data

sensor_stream.py

def sensor():
    import random
    while True:
        yield random.randint(1, 100)

gen = sensor()
print(next(gen))  # simulated sensor reading

Pagination Generator

pagination.py

def paginate(items, size):
    for i in range(0, len(items), size):
        yield items[i:i+size]

pages = paginate(list(range(20)), 5)
for p in pages:
    print(p)

Prime Numbers Generator

prime_generator.py

def is_prime(n):
    if n < 2: return False
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

def primes(limit):
    for x in range(limit):
        if is_prime(x):
            yield x

print(list(primes(20)))

Conclusion 🎉

>>“Generators make Python fast, scalable, and elegant — perfect for handling big or infinite data.” ✨

You now fully understand Generators in Python! Want the next topic? Try Decorators, Modules, Iterators, or OOP (Classes & Objects). Just tell me! 😊

🌱 Python yield — The Heart of Generators

Introduction 🌟

The yield keyword is used in a function to turn it into agenerator. Unlike return, which ends a function completely,yield pauses the function, saves its state, and returns a value. When the function is called again, it resumes exactly where it left off.

Note

💡 yield = pause + return
💡 return = stop + return

1. Basic Example 🧱

yield_basic.py

def demo():
    yield 1
    yield 2
    yield 3

for x in demo():
    print(x)

✔️ Each yield produces one value at a time.

2. How yield Works Internally 🧠

  • The function starts execution.
  • When yield is reached → it returns a value and pauses.
  • Next call resumes from the paused line.
  • Ends when no more yield statements are left.

3. Difference Between return and yield ⚔️

returnyield
Ends function completelyPauses function
Returns one valueReturns multiple values lazily
Function cannot resumeFunction resumes on next call
Memory-heavyMemory efficient

4. yield in a Loop 🔁

yield_loop.py

def count(n):
    for i in range(1, n + 1):
        yield i

for x in count(5):
    print(x)

✔️ Produces numbers from 1 to 5, one at a time.

5. Using next() with yield 🎮

yield_next.py

gen = count(3)

print(next(gen))  # 1
print(next(gen))  # 2
print(next(gen))  # 3
# next(gen)StopIteration

Note

✔️ StopIteration is raised when the generator is exhausted.

6. Multiple yield Statements 🧩

yield_multiple.py

def mixed():
    yield 10
    yield "hello"
    yield True

print(list(mixed()))

7. yield from — Delegating to Sub-Generators 🔗

yield_from.py

def gen1():
    yield from [1, 2, 3]

def gen2():
    yield from gen1()
    yield 4

print(list(gen2()))

✔️ Combines multiple generators cleanly.

8. Using yield for Infinite Sequences ♾️

yield_infinite.py

def infinite_counter():
    n = 1
    while True:
        yield n
        n += 1

gen = infinite_counter()
print(next(gen))
print(next(gen))
print(next(gen))

Note

⚠️ Be careful with infinite loops.

9. Returning Final Value with StopIteration 🛑

yield_return.py

def sample():
    yield 1
    yield 2
    return "Done"   # This becomes StopIteration value

gen = sample()
try:
    while True:
        print(next(gen))
except StopIteration as e:
    print("Final:", e.value)

✔️ return inside a generator signals a final value.

10. Sending Data Back into Generator (send()) 📤

yield_send.py

def greeter():
    name = yield "Enter your name:"
    yield f"Hello {name}"

gen = greeter()
print(next(gen))        # Startasks for name
print(gen.send("Sathish"))

11. Real-World Examples 🌍

✔ Reading Large Files Efficiently

yield_file.py

def read_lines(filename):
    with open(filename) as file:
        for line in file:
            yield line.strip()

for line in read_lines("data.txt"):
    print(line)

✔ Pagination System

yield_pagination.py

def paginate(data, size):
    for i in range(0, len(data), size):
        yield data[i:i+size]

for page in paginate(range(20), 5):
    print(list(page))

✔ Sensor Data Stream

yield_sensor.py

import random

def sensor():
    while True:
        yield random.randint(1, 100)

gen = sensor()
print(next(gen))

12. When to Use yield? 🎯

  • When dealing with large datasets.
  • When creating infinite sequences.
  • When building custom iterators.
  • When memory efficiency is required.
  • When streaming data line-by-line.

Conclusion 🎉

>>yield transforms ordinary functions into powerful, memory-efficient iterators.” ✨

You now fully understand the yield keyword! Want the next topic? Try Decorators, Iterators, Modules, or OOP (Classes & Objects). Just tell me! 😊