Let's talk about one of the more advanced built-in functions in Python: the next
function.
You can use the next
function to get the next line from a file:
>>> with open("poems/harlem.txt", mode="rt") as file:
... first_line = next(file)
...
>>> first_line
'What happens to a dream deferred?\n'
The next
function doesn't just work on file objects though.
For example, it also works on csv.reader
objects:
>>> import csv
>>> with open("penguins_small.csv", mode="rt", newline="") as penguins_file:
... reader = csv.reader(penguins_file)
... headers = next(reader)
...
>>> headers
['species', 'island', 'bill_length_mm', 'bill_depth_mm', 'flipper_length_mm', 'body_mass_g', 'sex']
We just used next
to pop off the header row just before we start reading a CSV file.
Besides file objects and csv.reader
objects, what else does next
work with?
next
on most iterablesIf you pass a list to the next
function, Python will raise a TypeError
>>> numbers = [2, 1, 3, 4, 7]
>>> next(numbers)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator
The next
function doesn't work on lists.
It also doesn't work on dictionaries, sets, strings, tuples, or most other iterables.
What type of object does next
work with?
next
Python's next
function is designed to be used with iterators.
You can think of an iterator in two ways:
Files are iterators, but so are generators.
Here we're using a generator expression to make a generator object and then using next
on that generator:
with open("article.md") as file:
first_non_header = next(
line
for line in file
if line.strip() and not line.startswith("#")
)
Most of Python's looping helpers also return iterators.
For example the return value from the enumerate
and reversed
built-in functions are iterators.
So we could get the last item in a reversible iterable (such as a dictionary), like this:
>>> color_counts = {"blue": 2, "green": 1, "orange": 5, "purple": 3}
>>> last_key = next(reversed(color_counts))
>>> last_key
'purple'
This includes pretty much all the looping helpers in Python's itertools
module:
>>> from itertools import count
>>> c = count()
>>> next(c)
0
>>> next(c)
1
So next
works with files, generators, and any other iterator.
But what if we wanted to get the first item from an iterable that isn't an iterator, like a dictionary?
Is that possible with next
?
The answer is yes, but with an extra step.
next
with any iterableLet's say we have a dictionary and we'd like to get the first item from it:
>>> color_counts = {"blue": 2, "green": 1, "orange": 5, "purple": 3}
Dictionaries are iterables.
You can get an iterator from any iterable in Python by passing it to the built-in iter
function.
>>> color_iterator = iter(color_counts)
And iterators work with next
:
>>> first_key = next(color_iterator)
>>> first_key
'blue'
Since iterators are also iterables, they even work with the built-in iter
function:
>>> iterator_of_iterator = iter(color_iterator)
Which means we can use next
to get the first item from any iterable as long as we pass that iterable to the built-in iter
function first:
first_item = next(iter(any_iterable))
For more on the relationship between iterables and iterators, see the iterator protocol.
If next
is given an empty iterator, it will raise a StopIteration
exception:
>>> color_counts = {}
>>> next(iter(color_counts))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
To suppress that StopIteration
exception, you can supply a default value to the next
function.
>>> next(iter(color_counts), 0)
0
Whenever next
is used with a default value, that value will be returned whenever the given iterator is empty.
for
loops over repeated next
callsIf you need to get the next item from an iterable repeatedly, using a for
loop is probably preferable to manually calling next
over and over.
numbers = [2, 1, 3, 4, 7]
for number in numbers:
print(number)
If we were to implement that same for
loop using iter
and next
calls, our code would be much less readable:
number_iterator = iter(numbers)
while True:
try:
number = next(number_iterator)
print(number)
except StopIteration:
break
Python's for
loops are iterator-powered and they do low-level looping work for us.
next
Python's for
loops are powerful and they're usually preferable to next
.
So when is next
used?
The most common use case you'll likely have for next
is to pop off just one value from a generator, file, or other iterator.
import csv
with open("penguins_small.csv", mode="rt", newline="") as penguins_file:
reader = csv.reader(penguins_file)
headers = next(reader)
Sometimes you'll just need that value on its own. But sometimes you'll want to skip over the first value before looping over the rest of your items:
import csv
with open("penguins_small.csv", mode="rt", newline="") as penguins_file:
reader = csv.reader(penguins_file)
headers = next(reader) # We're popping the header row off first
for row in reader:
... # Then we'll process non-header rows in some way
There are other uses of next
but they're lower-level and more advanced
next
What are those more advanced uses of next
?
Primarily next
is used to implement looping helpers.
When a generator function or another iterator object wants to augment the way for
loops work, it might be necessary to manually call iter
and next
.
For example the implementation of the built-in zip
function (which allows for looping over multiple iterables at the same time in Python) looks pretty much like this:
def zip(*iterables):
iterators = [iter(it) for it in iterables]
sentinel = object()
while iterators:
result = []
for it in iterators:
elem = next(it, sentinel)
if elem is sentinel:
return
result.append(elem)
yield tuple(result)
That function manually calls next
on iterators corresponding to each of the given iterables, one after the other.
If you're curious about that sentinel = object()
line, see Unique sentinel values in Python.
next
for consuming a single iterator itemIn general, next
is typically used to consume a single item from an iterator.
That iterator might be a file object (we're skipping the first two lines here):
with open("poems/harlem.txt", mode="rt") as file:
next(file) # Skip first line
next(file) # Skip second line
for line in file:
print(line.strip())
Or that iterator might be a generator or a looping helper:
>>> from pathlib import Path
>>> readme_path = next(
... path
... for path in Path().rglob("*.md")
... if path.stem.lower() in ('readme', 'read me')
... )
>>> readme_path
PosixPath('README.md')
Here we've used next
on a generator object to find the first markdown file named readme
or read me
while ignoring the capitalization of the filename.
If you find next
confusing, you can always use a for
loop and a break
statement instead:
my_iterable = [2, 1, 3, 4, 7, 11]
# Get the first element of the iterable
first_item = None
for item in my_iterable:
first_item = item
break
print(first_item)
For more on iterators, see iterators in Python and Python's Iterator Protocol.
Need to fill-in gaps in your Python skills?
Sign up for my Python newsletter where I share one of my favorite Python tips every week.
Need to fill-in gaps in your Python skills? I send weekly emails designed to do just that.