Chapter 11: Iterators and Generators

Iterators and generators are advanced Python features that allow for efficient looping and data generation. They enable you to handle large datasets and compute values lazily, improving performance and memory usage.

Iterators

An iterator is an object that implements the __iter__() and __next__() methods. It represents a sequence of values, returning one value at a time when iterated.

Example of an Iterator:

# Create an iterator
numbers = iter([1, 2, 3, 4, 5])

# Access elements using next()
print(next(numbers))  # Output: 1
print(next(numbers))  # Output: 2

# Iterate over the rest using a loop
for num in numbers:
    print(num)  # Output: 3, 4, 5

Key Methods:

Method

Description

__iter__()

Returns the iterator object itself.

__next__()

Returns the next item in the sequence or raises StopIteration.

Custom Iterators

You can create custom iterators by defining a class that implements the __iter__() and __next__() methods.

Example:

Generators

A generator is a special type of iterator created using functions and the yield keyword. Generators are used to lazily produce values one at a time, as needed.

Creating a Generator:

Key Differences Between Generators and Iterators:

  1. Generators are defined using functions and the yield keyword.

  2. Generators automatically implement the __iter__() and __next__() methods.

Generator Expressions

Generator expressions are similar to list comprehensions but produce values lazily.

Example:

Advantages of Generators

  1. Memory Efficiency: Generate values on the fly instead of storing them in memory.

  2. Lazy Evaluation: Values are computed only when needed.

  3. Readable Code: Easier to implement compared to custom iterators.

Practical Use Cases

  1. Reading Large Files: Process a file line by line without loading the entire file into memory.

  2. Infinite Sequences: Generate an unbounded sequence of values.

  3. Efficient Data Pipelines: Process data streams incrementally.

Exercises

Exercise 1:

Write a custom iterator that generates the Fibonacci sequence up to a given number n.

Solution:

Exercise 2:

Write a generator function to yield even numbers up to a given number n.

Solution:

Exercise 3:

Use a generator expression to create a sequence of cubes for numbers 1 through 5.

Solution:

Best Practices

  1. Use generators for processing large datasets or streams to save memory.

  2. Combine generator expressions with functions like sum() and max() for concise calculations.

  3. Prefer yield over returning large lists when possible.

  4. Test custom iterators to handle edge cases, such as empty sequences.

In the next chapter, we will explore decorators and context managers, which are powerful tools for enhancing and managing Python functions and resources.

Last updated