ch7s3_GeneratorsAndIterators

Generators and iterators form the foundation of **lazy evaluation** in Python.

Chapter 7: Functional Programming

Sub-Chapter: Generators and Iterators โ€” Lazy and Efficient Data Processing

Generators and iterators form the foundation of lazy evaluation in Python.
They allow you to handle sequences of data one element at a time, making your programs memory-efficient, stream-friendly, and ideal for large datasets or infinite streams.


๐Ÿงฉ 1. Iterables vs. Iterators

Iterable

An iterable is any Python object that can return an iterator โ€” for example, lists, tuples, strings, sets, and dictionaries.

nums = [1, 2, 3]
it = iter(nums)
print(next(it))  # 1
print(next(it))  # 2

Iterator

An iterator is an object with two methods:

Example of a custom iterator:

class Counter:
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self

    def __next__(self):
        if self.current > self.end:
            raise StopIteration
        self.current += 1
        return self.current - 1

for num in Counter(1, 5):
    print(num)  # 1, 2, 3, 4, 5

โœ… Every generator is an iterator, but not every iterator is a generator.


โš™๏ธ 2. The Iterator Protocol

MethodPurpose
__iter__()Returns the iterator object
__next__()Returns the next item or raises StopIteration
iter(obj)Returns an iterator for an iterable
next(iterator)Fetches the next item manually

๐Ÿง  3. Generators โ€” Functions that Remember State

A generator is a special kind of iterator created with a yield statement inside a function.

def countdown(n):
    while n > 0:
        yield n
        n -= 1

for num in countdown(3):
    print(num)
# Output: 3, 2, 1

Each time the generator yields, Python suspends its state until the next call.


โšก 4. Lazy Evaluation in Action

Generators process data on demand โ€” no values are computed until requested.

def infinite_sequence():
    num = 0
    while True:
        yield num
        num += 1

gen = infinite_sequence()
print(next(gen))  # 0
print(next(gen))  # 1
print(next(gen))  # 2

โš ๏ธ Infinite generators must be consumed carefully to avoid endless loops.


๐Ÿ”„ 5. Generator Expressions โ€” Memory-Efficient Alternatives

Generator expressions are compact generators defined inline using parentheses.

squares = (x ** 2 for x in range(10))
print(sum(squares))  # 285

Comparison:

ComprehensionTypeMemory Usage
[x ** 2 for x in range(10)]ListCreates full list in memory
(x ** 2 for x in range(10))GeneratorProduces values lazily

๐Ÿงฑ 6. yield from โ€” Delegating to Subgenerators

You can delegate iteration to another generator using yield from, simplifying nested loops.

def generator_a():
    yield from range(3)
    yield from ["a", "b", "c"]

for value in generator_a():
    print(value)

Output:

0
1
2
a
b
c

๐Ÿงฎ 7. Advanced Generator Operations

Sending Values into a Generator

You can use .send() to send data back into a running generator.

def echo():
    while True:
        value = yield
        print("Received:", value)

g = echo()
next(g)         # Prime the generator
g.send("Hello") # Received: Hello
g.send("World") # Received: World

Closing a Generator

def endless():
    while True:
        yield "Running..."

g = endless()
print(next(g))
g.close()  # Gracefully stops the generator

๐Ÿ“š 8. Itertools โ€” Power Tools for Iteration

The itertools module enhances generators with combinatorial and infinite tools.

import itertools

count = itertools.count(10, 2)        # 10, 12, 14, ...
cycled = itertools.cycle(["A", "B"])  # A, B, A, B, ...
limited = itertools.islice(count, 5)  # Take first 5 values

print(list(limited))  # [10, 12, 14, 16, 18]

Other useful itertools functions:


๐Ÿงพ 9. Real-World Examples

Example 1 โ€” Fibonacci Sequence

def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print([next(fib) for _ in range(10)])

Example 2 โ€” Lazy File Reader

def read_large_file(path):
    with open(path) as file:
        for line in file:
            yield line.strip()

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

Example 3 โ€” Streaming Data Pipeline

def get_data():
    for i in range(1, 1000000):
        yield i

def process(data):
    for n in data:
        yield n * 2

def filter_even(data):
    for n in data:
        if n % 2 == 0:
            yield n

# Combine pipeline
stream = get_data()
stream = process(stream)
stream = filter_even(stream)

for val in stream:
    if val > 20:
        break
    print(val)

๐Ÿง  10. Memory and Performance

StructureData SizeMemoryEvaluation
ListEagerHighImmediate
GeneratorLazyLowOn-demand

Generators let you process infinite or massive datasets without exhausting RAM.


๐Ÿ”„ 11. Asynchronous Generators (Advanced)

Async generators allow asynchronous iteration in concurrent code:

import asyncio

async def async_counter():
    for i in range(3):
        yield i
        await asyncio.sleep(1)

async def main():
    async for value in async_counter():
        print(value)

asyncio.run(main())

๐Ÿงญ 12. Visualization โ€” Lazy Data Flow

Iterable โ†’ Iterator โ†’ Generator โ†’ Consumer
  |           |           |          |
  v           v           v          v
[1,2,3]  โ†’  __next__() โ†’ yield n โ†’ print()

๐Ÿงพ 13. Best Practices

โœ… Use generators for large or infinite sequences.
โœ… Prefer generator expressions for quick, one-liners.
โœ… Combine with itertools for advanced iteration.
โœ… Avoid converting large generators to lists.
โœ… Use yield from to simplify nested loops.
โœ… Always handle StopIteration when manually consuming.
โœ… For concurrency, use async generators.


๐Ÿง  Summary

ConceptDescriptionExample
IteratorObject with __iter__() and __next__()iter([1,2,3])
GeneratorFunction that yields values lazilyyield n
Generator ExpressionInline generator(x**2 for x in data)
yield fromDelegates to another iterableyield from range(5)
Async GeneratorYields in async contextasync def ... yield

Mastering generators and iterators gives you full control over how data flows through your programs โ€” efficiently, lazily, and elegantly.