Iterators in Python


In Python, iteration is a fundamental concept, and understanding how iterators work is essential for writing efficient, readable, and Pythonic code. An iterator is an object in Python that allows you to traverse through all the elements of a collection, like a list or tuple, without the need to manually access each element.

In this guide, we'll explore Python iterators, how they work, how to create them, and how to use them in various situations. Whether you're new to Python or an experienced developer, this comprehensive post will enhance your understanding of iterators and help you master this powerful feature of Python.


What is an Iterator in Python?

An iterator in Python is an object that implements two methods, __iter__() and __next__(). These methods allow the object to be iterated upon (typically in a loop like for).

  • __iter__(): This method initializes the iterator and returns the iterator object itself.
  • __next__(): This method returns the next item in the sequence. When there are no more items to return, it raises the StopIteration exception to signal the end of the iteration.

Example: Basic Iterator in Python

Here's a simple example that demonstrates how an iterator works:

class MyIterator:
    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
        else:
            self.current += 1
            return self.current - 1

# Creating an iterator object
my_iter = MyIterator(1, 5)

# Iterating over the object
for num in my_iter:
    print(num)

Output:

1
2
3
4
5

In this example:

  • __iter__() returns the iterator object.
  • __next__() returns the next value in the sequence and raises StopIteration once the iteration is complete.

How Iterators Work in Python

Python's for loop and several built-in functions are designed to work with iterators. Internally, when you loop over a collection, Python calls the __next__() method repeatedly until it encounters StopIteration.

Example: Using an Iterator with a for Loop

A simple way to create an iterator is by using built-in collections like lists, tuples, and strings, all of which are inherently iterable:

numbers = [1, 2, 3, 4, 5]

for num in numbers:
    print(num)

Output:

1
2
3
4
5

Here, numbers is an iterable, and the for loop automatically handles the iteration by calling __iter__() and __next__() on the numbers list.


Creating Your Own Iterator

You can create custom iterators in Python by implementing the __iter__() and __next__() methods in a class. This is useful when you need to define a custom sequence or iteration logic.

Example: A Custom Iterator for Fibonacci Sequence

Let’s create an iterator for generating the Fibonacci sequence:

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

    def __iter__(self):
        return self

    def __next__(self):
        if self.a > self.limit:
            raise StopIteration
        else:
            result = self.a
            self.a, self.b = self.b, self.a + self.b
            return result

# Create an iterator for Fibonacci numbers less than 50
fib_iter = FibonacciIterator(50)

for num in fib_iter:
    print(num)

Output:

0
1
1
2
3
5
8
13
21
34

In this example:

  • The FibonacciIterator class generates the Fibonacci numbers one by one.
  • The __next__() method updates the sequence and raises StopIteration when the limit is reached.

Iterators vs. Iterables in Python

It's important to understand the difference between iterables and iterators:

  • Iterable: An object is an iterable if it can return an iterator (it implements the __iter__() method). Examples of iterables include lists, strings, and dictionaries.
  • Iterator: An iterator is an object that keeps track of the current state of the iteration and knows how to return the next item. It implements both the __iter__() and __next__() methods.

Example: Distinction Between Iterable and Iterator

# List is an iterable
numbers = [1, 2, 3, 4]
iterator = iter(numbers)  # Creating an iterator from the list

# Using the iterator
print(next(iterator))  # Output: 1
print(next(iterator))  # Output: 2
  • numbers is an iterable, but to iterate over it, we need to convert it into an iterator using iter().
  • The next() function retrieves the next item from the iterator.

Common Built-in Iterators in Python

Python provides a variety of built-in iterators and iterable objects that are incredibly useful for developers.

Example 1: Using range() as an Iterator

The range() function returns an iterator that generates numbers starting from 0 up to (but not including) a given number:

for i in range(5):
    print(i)

Output:

0
1
2
3
4

In this case, range(5) creates an iterator that generates numbers from 0 to 4.


Example 2: Using enumerate() with Iterators

The enumerate() function returns an iterator that generates pairs of an index and an element from an iterable:

fruits = ['apple', 'banana', 'cherry']

for index, fruit in enumerate(fruits):
    print(f"{index}: {fruit}")

Output:

0: apple
1: banana
2: cherry

Here, enumerate() helps track the index of each item in the iterable.


Example 3: Using zip() to Combine Iterators

The zip() function combines multiple iterables into a single iterator of tuples:

names = ['Alice', 'Bob', 'Charlie']
scores = [85, 90, 88]

for name, score in zip(names, scores):
    print(f"{name}: {score}")

Output:

Alice: 85
Bob: 90
Charlie: 88

In this case, zip() pairs the corresponding elements from two lists into tuples and returns an iterator.


When to Use Iterators in Python

Iterators are particularly useful in the following scenarios:

  1. Memory Efficiency: Iterators generate items on the fly, so they are more memory-efficient compared to lists or other collections that store all elements in memory at once.
  2. Lazy Evaluation: Iterators allow for lazy evaluation, meaning items are generated only when requested (useful for working with large datasets).
  3. Custom Sequences: When you need to define your own sequence or iteration logic (such as generating prime numbers, Fibonacci numbers, etc.), iterators provide a clean and efficient solution.