In Python, an iterator is an object which has a __next__ method. Iterators are used to represent sequences like lists, tuples, strings etc. When calling the __next__ method, an iterator gives the next element in the sequence. Iterators will raise a StopIteration exception when there are no more elements to iterate.

Python’s built-in sequences like the list, tuple, string etc. can be converted to iterator objects by calling the built-in function iter(). The built-in next() function can be used to get the next element from an iterator. This is illustrated below.

str_obj = 'abc'
str_iterator = iter(str_obj)  # Creates iterator object
next(str_iterator)  # Returns 'a'
next(str_iterator)  # Returns 'b'
next(str_iterator)  # Returns 'c'
next(str_iterator)  # Raises StopIteration exception

A for loop uses this behind the scenes. The operation of a for loop can be roughly summarised as follows. When you give a sequence to a for loop, it calls the iter() function and converts the sequence into an iterator object. On each iteration, the for loop uses next() function to get the next element in the sequence. The StopIteration exception is a signal for the for loop to terminate.

Creating Your Own Iterators

It is fairly simple to create your own iterators. Iterators are normal Python classes with some special methods. All you need is a class which contains an __iter__() method and a __next__() method. The Python interpreter will call __iter__() method when you call the iter() function with an iterator object. When you call the next() function with an iterator object, the interpreter will call the __next__ method in your class.

The following example illustrates a simple iterator to generate a sequence of numbers.

class Counter:
    def __init__(self, limit):
        self.limit = limit
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count == self.limit:
            raise StopIteration
        self.count += 1
        return self.count

counter = Counter(3)  # Creates counter object
iterator = iter(counter)  # Creates iterator object
next(iterator)  # Retruns 1
next(iterator)  # Retruns 2
next(iterator)  # Retruns 3
next(iterator)  # Raises StopIteration exception

We defined a class Counter with methods __iter__ and __next__. When initialising a new object, we are creating a variable count to keep track of the iteration. Since our class itself is an iterator class, the __iter__ method can return the current object(self).

In the above example, we called iter() and next() functions manually. In real-world programs, we rarely do this. A more realistic use case will be using a for loop to iterate over our counter objects.

class Counter:
    def __init__(self, limit):
        self.limit = limit
        self.count = 0

    def __iter__(self):
        return self

    def __next__(self):
        if self.count == self.limit:
            raise StopIteration
        self.count += 1
        return self.count

counter = Counter(5)
for number in counter:
    print(number)
Output:

1
2
3
4
5

A slightly more complex iterator to reverse a string is given below.

class StrReverse:
    def __init__(self, string):
        self.string = string
        self.tracker = len(string)

    def __iter__(self):
        return self

    def __next__(self):
        if self.tracker == 0:
            raise StopIteration
        self.tracker -= 1
        return self.string[self.tracker]

reverser = StrReverse('abcd')
for char in reverser:
    print(char)
Output:

d
c
b
a

Generators

Generators are a special type of functions used to create iterators. Generators use yield statement instead of return. The counter iterator in the previous section can be implemented using generators as follows:

def counter(number):
    for n in range(number):
        yield n

counter_obj = counter(5)
for count in counter_obj:
    print(count)
Output:

0
1
2
3
4

Generators greatly simplify the creation of iterators by implementing __iter__ and __next__ methods for you. You may have noticed that the output is different from Counter iterator in the previous section. The iterator gave you 1 to 5 while the generator gave you 0 to 4. You may try modifying the generator to give 1 to 5.

We can convert the string reverse iterator to a generator as follows:

def str_reverse(string):
    for tracker in range(len(string)-1, -1, -1):
        yield string[tracker]

for char in str_reverse('abcd'):
    print(char)
Output:

d
c
b
a

Inside the generator function, we are using the range function to count from the last index of string to 0 and uses the tracker to keep track of this count.

Generator Expression

Generator expressions are similar to list comprehensions but return a generator instead of a list. Generator expressions use parentheses just like list comprehensions use square brackets. It can be used in anywhere a list (or any iterable) is expected.

For example, the built-in sum function which accepts a list of numbers can also accept a generator expression. The following code calculates the sum of numbers from 1 to 10.

sum(number for number in range(1, 10))  # Outputs 45

This is equivalent to

numbers = [number for number in range(1, 10)]
sum(numbers)  # Outputs 45

Last updated on April 1, 2016
Tags: Python Iterators Generators Generator Expressions