Iterators in Python (with Examples)

Iterators in Python are a powerful feature that allows us to traverse through all elements of a collection (like lists, tuples, or dictionaries) without exposing the collection’s underlying structure. Understanding iterators is essential for writing efficient, memory-friendly Python programs.

Iterators in Python

In Python, iterators are objects that enables us to traverse through elements of a collection (like lists, tuples, or dictionaries) one at a time. Iterator concept is common to Python’s for-loops and functions like map() and filter().

An iterator allows us to access elements of a collection without needing to know its underlying structure or maintain an index.

Iterable and Iterator

Iterable – An iterable is an object that contains a collection of values that we can loop over (like lists, strings, and tuples). We can convert an iterable into an iterator using Python’s built-in iter() function. Examples of iterable are lists, strings, tuples, dictionary etc.

Iterator – An iterator is an object that represents a stream of data. It is initialized using the iter() function and allows us to fetch one element at a time from the iterable using the next() function. Once all elements are consumed, calling next() raises a StopIteration exception.

  • An iterable can be converted to an iterator using iter().
  • An iterator can fetch the next item using next() and raises StopIteration when done.
# Example of iterable and iterator
# This list is an iterable
courses_list = ["Generative AI", "Python", "Prompt Engineering", "Power BI"]

# using iter we created an iterator over the list
iterator = iter(courses_list)

# access elements from iterable - Uusing iterator next() function
print(next(iterator))  # Output => Generative AI
print(next(iterator))  # Output => Python
print(next(iterator))  # Output => Prompt Engineering

# next(iter_obj) is same as iter_obj.__next__()
print(iterator.__next__())  # Output => Power BI
# Raises StopIteration
  • In this example, courses_list is an iterable.
  • Using iter(courses_list) we have created an iterator over the courses_list.
  • Using next(iterator) OR iterator.__next__(), we can access the next element of the iterable.
  • When iterator reached end of courses_list, then it will raise StopIteration error.

Iterator with for loop

courses_list = ["Generative AI", "Python", "Prompt Engineering", "Power BI"]
course_iter = iter(courses_list) # create iterator over the list

# infinite while loop
while True:
    try:
        element = next(course_iter)  # access next element from iterator
        print(element)
    except StopIteration:      # catch StopIteration error raise by iterator
        break                  # break the loop

Create Custom Iterator

In Python, an object is considered an iterator if it implements the iterator protocol, which consists of two methods:

  • __iter__() – Returns the iterator object itself.
  • __next__() – Returns the next item from the collection (iterable). If no more items are left, it raises the StopIteration exception.

We can create our own iterators (custom iterators) by defining a class that implements both __iter__() and __next__() methods.

class PowerElement:
    """Class to implement an iterator to calculate power of all numbers
    from 1 to given number"""

    def __init__(self, number, power):  # class __init__ function
        self.number = number
        self.power = power

    def __iter__(self):                 # __iter__ function to define iterator
        self.counter = 1
        return self

    def __next__(self):                 # __next__ function to get next element from iterator
        if self.counter <= self.number:
            result = self.counter ** self.power
            self.counter += 1
            return result
        else:
            raise StopIteration


# create a class object
power_element_object = PowerElement(4, 3)

# create an iterable from iterator class object
i = iter(power_element_object)

# Using next to get to the next iterator element
print(next(i))
print(next(i))
print(next(i))
print(next(i))
# print(next(i))  # raise StopIteration error
  • In this example, we have created PowerElement class, which defines __init__, __iter__ and __next__ function
  • __init__(self, number, power) is a constructor to the class, which sets initial value to class attributes
  • __iter__(self) make the class an iterator and return its own object.
  • __next__(self) provide the logic to calculate the next value from iterator. In this case we are calculating the power of all the numbers from 1 to given number.
  • At end __next__ function raise StopIteration error.

Built-in Functions Returning Iterators

Python has many built-in functions that return iterators:

  • range(start, end) – This generates numbers from start to end - 1.
  • map(function, iterable) – Applies a function to each item of an iterable.
  • filter(function, iterable) – Filters elements based on a function.
  • zip(iterable1, iterable2, ...) – Pairs elements from multiple iterables.
# Using range (returns an iterator)
for i in range(4):
    print(i)  # Output => 0 1 2 3

# Using map (returns an iterator)
number_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
square_numbers = map(lambda x: x ** 2, number_list)
print(list(square_numbers))  # Output => [1, 4, 9, 16, 25, 36, 49, 64, 81]

# Use filter to get words longer than 3 characters
numbers_divisble_by_3 = filter(lambda number: number % 3 == 0, number_list)
# Convert the result to a list and print it
print(list(numbers_divisble_by_3))  # Output => [3, 6, 9]

cube_of_numbers = map(lambda x: x ** 3, number_list)
# Use zip to combine names and scores
zip_cube_of_numbers = zip(number_list, list(cube_of_numbers))
# Convert the result to a list and print it
print(list(zip_cube_of_numbers))
# Output => [(1, 1), (2, 8), (3, 27), (4, 64), (5, 125), (6, 216), (7, 343), (8, 512), (9, 729)]

Key Properties of Iterator

  • Lazy Evaluation – Iterators do not compute or store all the values upfront; instead, they generate each value only when needed, one at a time. This makes them memory efficient and ideal for working with large datasets or infinite sequences. next(iterator) – only this element is computed now
  • Single Traversal (One-time Use) – Iterators are exhausted after one complete traversal. Once we iterate over all the elements, the iterator cannot be reused. After reaching the end, any subsequent call to next() will raise a StopIteration exception.
  • Stateful – Iterators maintain an internal state to keep track of their current position in the iterable. Each call to next() moves the iterator forward. This internal state is why we cannot reset an iterator without creating a new one.
  • Implements the Iterator Protocol – An object is considered an iterator if it implements the iterator protocol, which consists of two methods => __iter__() and __next__()
  • Memory Efficient – Iterators are more memory-efficient than lists or other collections because they don’t store all elements in memory. Instead, they generate each element on the fly when needed. This is particularly useful when working with large datasets or streams of data.
  • No Random Access – Iterators do not support random access (i.e., we cannot access an arbitrary element like we can with lists using an index). The only way to access the next element is by using next(). Once an element is retrieved, it cannot be revisited unless we recreate the iterator.
  • Use in Loops – Iterators work seamlessly in for loops, and Python’s for loop is designed to work with iterators. The loop automatically calls next() on the iterator and handles the StopIteration exception behind the scenes.
  • Can be Infinite – Since iterators generate items one at a time, they can represent infinite sequences. We can have an iterator that never stops yielding values, which is useful in scenarios where we don’t know how many items we need upfront.

Use Cases and Benefits of Iterators

  • Memory Efficiency – Because iterators are lazy, they are memory efficient. Iterators are particularly useful when dealing with large datasets or infinite sequences.
  • Stream Processing – Iterators make it easy to process streams of data, like reading from a file or fetching database records. We can handle them piece by piece without loading everything into memory at once.
  • Pipelining – Since iterators return one element at a time, they can be chained together to create a pipeline of transformations.

Common Mistakes to avoid

  • Reusing an Iterator – An iterator can only be traversed once. After it raises StopIteration, it becomes exhausted. If we want to iterate over the same data again, then we need to create a new iterator.
  • Modifying the Iterable During Iteration – If we modify the underlying iterable (like a list) while iterating over it, we may encounter unexpected behavior or errors. It’s generally best to avoid modifying a collection during iteration.

Summary

Iterators in Python provide a powerful way to process data sequentially, enabling efficient memory usage and lazy evaluation. With built-in support for iterators in loops and functions, as well as the ability to create custom iterators and generators, Python offers a highly flexible and efficient iteration model.

In this article we learned about Iterators in Python. Following topics were covered:

Code – Github Repository

All code snippets and programs for this article and for Python tutorial, can be accessed from Github repository – Comments and Docstring in Python.

Python Topics


Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *