Python > Advanced Python Concepts > Iterators and Generators > Creating Custom Iterators

Custom Iterator for Fibonacci Sequence

This example demonstrates how to create a custom iterator in Python to generate the Fibonacci sequence. Custom iterators provide fine-grained control over how data is produced, allowing for efficient and memory-friendly sequence generation.

Code Implementation

This code defines a class FibonacciIterator that generates Fibonacci numbers up to a specified limit. The __init__ method initializes the starting values and the limit. The __iter__ method returns the iterator object itself. The __next__ method calculates the next Fibonacci number and returns it. When the limit is reached, it raises StopIteration to signal the end of the sequence.

class FibonacciIterator:
    def __init__(self, limit):
        self.limit = limit
        self.a = 0
        self.b = 1
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count < self.limit:
            self.count += 1
            fib_number = self.a
            self.a, self.b = self.b, self.a + self.b
            return fib_number
        else:
            raise StopIteration

# Example Usage:
fib_iter = FibonacciIterator(10)
for num in fib_iter:
    print(num)

Concepts Behind the Snippet

Iterators are objects that allow you to traverse through a collection of data. To create a custom iterator in Python, you need to define a class with two essential methods: __iter__() and __next__(). __iter__() should return the iterator object itself. __next__() should return the next item in the sequence and raise StopIteration when there are no more items.

Real-Life Use Case

Custom iterators are useful when dealing with large datasets or infinite sequences. For example, you can create an iterator to read data from a very large file chunk by chunk, processing each chunk as it is read, without loading the entire file into memory. They are also applicable when generating mathematical sequences like prime numbers or Fibonacci numbers where you want to compute values on demand.

Best Practices

  • Ensure that your __next__() method correctly raises StopIteration when the sequence is exhausted to prevent infinite loops.
  • Consider implementing a reset() method to allow the iterator to be reused.
  • Handle potential errors gracefully, such as invalid input to the iterator's constructor.

Interview Tip

Be prepared to explain the difference between iterators and iterables. An iterable is an object that can be iterated over (e.g., a list), while an iterator is an object that actually performs the iteration. You should also be able to explain the role of the __iter__() and __next__() methods.

When to Use Them

Use custom iterators when you need to generate a sequence of values on demand, especially when the sequence is very large or infinite. They are also appropriate when you need fine-grained control over the iteration process.

Memory Footprint

Iterators are memory-efficient because they generate values one at a time, rather than storing the entire sequence in memory. This is especially important when dealing with large datasets.

Alternatives

Generators, created using the yield keyword, provide a more concise way to create iterators in many cases. List comprehensions and generator expressions can also be used for simple sequence generation, but they might not be as flexible as custom iterators for complex scenarios.

Pros

  • Memory efficiency: Generate values on demand, avoiding storing large sequences in memory.
  • Fine-grained control: Customize the iteration process to meet specific requirements.
  • Lazy evaluation: Only compute values when they are needed.

Cons

  • More complex to implement than simple loops or list comprehensions.
  • Requires careful handling of the StopIteration exception.

FAQ

  • What is the difference between an iterator and an iterable?

    An iterable is an object that can be iterated over (e.g., a list, tuple, or string). An iterator is an object that performs the iteration, providing access to the elements of the iterable one at a time. An iterable has an __iter__() method that returns an iterator.
  • Why do I need to raise StopIteration?

    StopIteration is raised to signal that the iterator has reached the end of the sequence and there are no more elements to return. Without it, the iterator would continue to run indefinitely.
  • Can I reset an iterator?

    By default, iterators cannot be reset. Once they have reached the end of the sequence, they cannot be restarted. However, you can implement a reset() method in your custom iterator class to allow it to be reused.