Python Next Function: Read Data From Iterators

The next() function is useful when working with iterators and it’s a must-know for Python developers.

The Python next() function takes as the first argument an iterator and as an optional argument a default value. Every time next() is called it returns the next item in the iterator until no items are left. At that point the next function returns a default value (if passed to it) or a StopIterarion exception is raised.

In this tutorial, you will learn in what circumstances you can use the next() function as part of your Python programs.

Let’s get started!

What Does next() Do in Python?

The Python next function takes two arguments the first one is an iterator and it’s mandatory. The second one is a default value and it’s optional.

next(iterator[, default_value])

Every time you pass an iterator to the next function you get back the next item in the iterator.

For example, let’s define a Python list and then create an iterator using the iter() function.

>>> numbers = [1, 2, 3, 4]
>>> numbers_iterator = iter([1, 2, 3, 4])

Before testing the next function let’s have a look at the difference of the type returned by the Python interpreter for the list and for the iterator associated with the list.

>>> print(type(numbers))
<class 'list'>
>>> print(type(numbers_iterator))
<class 'list_iterator'> 

And now let’s see what we get back when we call the next function and pass our iterator to it:

>>> next(numbers_iterator)
1 

Here is what happens if we call the next function multiple times until the iterator doesn’t have any items left.

>>> next(numbers_iterator)
2
>>> next(numbers_iterator)
3
>>> next(numbers_iterator)
4
>>> next(numbers_iterator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration 

Once there are no more items in the iterator the Python interpreter raises a StopIteration exception.

How to Return a Default Value From the Python next Function

If you don’t want Python to raise a StopIteration exception when it reaches the end of an iterator you can also pass an optional default value to the next function.

Let’s take the same list we have used before but this time we will pass a default value to the next function.

>>> next(numbers_iterator, 'No more items left')
1
>>> next(numbers_iterator, 'No more items left')
2
>>> next(numbers_iterator, 'No more items left')
3
>>> next(numbers_iterator, 'No more items left')
4
>>> next(numbers_iterator, 'No more items left')
'No more items left' 

As you can see when the end of iterator is reached we don’t get back an exception anymore, instead, we get back the default string passed as an optional value to the next function.

Another option could be to return None as the default value if you want to easily verify programmatically when the end of the iterator is reached.

>>> while True:
...     next_value = next(numbers_iterator, None)
...     if next_value:
...             print(next_value)
...     else:
...             break
... 
1
2
3
4
>>>  

How is The next Function Related to the __next__ Method?

Some Python objects provide a method called __next__.

Do you know what is the difference between the __next__ method and the next() function?

When you call the next() function and pass an iterator to it the __next__ method of the iterator object is called.

I wonder if we can call the __next__ method of the iterator directly and get the same result back:

>>> numbers = [1, 2, 3, 4]
>>> numbers_iterator = iter([1, 2, 3, 4])
>>> numbers_iterator.__next__()
1
>>> numbers_iterator.__next__()
2
>>> numbers_iterator.__next__()
3
>>> numbers_iterator.__next__()
4
>>> numbers_iterator.__next__()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration 

Yes, we can!

So the behaviour of the __next__ method is the same as the next() function.

To give you a deeper understanding of how this works, let’s pass a list to the next() function instead of passing an iterator to it.

>>> numbers = [1, 2, 3, 4]
>>> next(numbers)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator 

The Python interpreter raises a TypeError exception because a list is not an iterator and it doesn’t implement the __next__ method.

>>> numbers.__next__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'list' object has no attribute '__next__' 

As you can see the list doesn’t have any attribute called __next__ because this method is not implemented in lists.

If we do the same check with a list iterator we get back details about its __next__ method.

>>> numbers_iterator = iter([1, 2, 3, 4])
>>> numbers_iterator.__next__
<method-wrapper '__next__' of list_iterator object at 0x7fb058255970> 

This shows why the next() function can be applied to iterators but not to iterables like lists.

Python Next Function and Generator Expressions

The next() function can also be used with Python generators.

Let’s take our list of numbers and create a generator expression to double each number in the list:

>>> numbers = [1, 2, 3, 4]
>>> numbers_generator = (2*number for number in numbers) 
>>> print(type(numbers_generator))
<class 'generator'> 

Now we will pass this generator to the next() function and see what it returns:

>>> next(numbers_generator)
2
>>> next(numbers_generator)
4
>>> next(numbers_generator)
6
>>> next(numbers_generator)
8
>>> next(numbers_generator)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration 

We get back the values we expected from the generator and a StopIteration exception is raised by the Python interpreter when it reaches the end of the generator.

As we have done before with our iterator we can confirm that also the generator implements the __next__ method that is called when the generator is passed to the next() function:

>>> numbers_generator.__next__
<method-wrapper '__next__' of generator object at 0x7fb0581f9430> 

In Python, every generator is an iterator. They both implement the __next__ method.

Use next to Get the First Item in an Iterable Matching a Condition

Let’s say you have an iterable, for example, a tuple, and you want to get the first item in the iterable that matches a specific condition.

The first way to do that is with a for loop

For instance, given the following tuple I want to know the first item greater than 10:

numbers = (3, 5, 9, 11, 13) 

With a for loop we would do the following:

>>> for number in numbers:
...     if number > 10:
...             print(number)
...             break
... 
11 

The other option is to use the next() function with a generator expression.

>>> next(number for number in numbers if number > 10)
11 

What if our condition doesn’t match any items in the generator?

>>> next(number for number in numbers if number > 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration 

In that case, a StopIteration exception is raised.

As we have seen in one of the sections above we can also pass a default value to the next() function to avoid this exception.

Let’s do that…

>>> next((number for number in numbers if number > 20), 'No item found')
'No item found' 

Notice that the first parameter passed of the next() function is a generator and the second parameter is the default value.

Python next Function Applied to a Generator with a Lambda Condition

In the previous code, we have used the next() function and a generator. We can also use a lambda function as a condition.

Given the same tuple we have used before, let’s write the generator expression using a lambda:

>>> numbers = (3, 5, 9, 11, 13)
>>> next(number for number in numbers if number > 10)

Notice how the way the if condition is written changes:

>>> condition = lambda x: x > 10
>>> next(number for number in numbers if condition(number))
11 

This allows us to make the if condition more generic.

Performance of a For Loop vs next Function

Using the Python next() function we can replicate the same behaviour of a for loop.

I’m wondering which one of the two approaches is the fastest.

Let’s create a list with 100,000 items using the Python range function.

numbers = list(range(100000)) 

We will capture the start time and end time for each implementation to see how long the execution of each implementation takes.

For Loop

import datetime 

numbers = list(range(1,100001))
start_time = datetime.datetime.now() 

for number in numbers:
    print(number)

end_time = datetime.datetime.now()
print("Execution time: {}".format(end_time - start_time)) 
Execution time: 0:00:00.163049 

Next with Iterator

import datetime 

numbers = iter(range(1,100001))
start_time = datetime.datetime.now() 

while True:
    next_value = next(numbers, None) 

    if next_value:
        print(next_value)
    else:
        break

end_time = datetime.datetime.now()
print("Execution time: {}".format(end_time - start_time)) 
Execution time: 0:00:00.177238 

The for loop is faster than the next() function using the iterator.

Conclusion

In this tutorial, we have seen how you can use the Python next() function in your programs.

You should also have an understanding of the way iterators and generators work.

Now, how will you use the next() function in your code? 🙂

Leave a Comment