Функция net Python: чтение данных из итераторов

Функция next() полезна при работе с итераторами и ее обязательно нужно знать разработчикам Python.

Функция Python next() принимает в качестве первого аргумента итератор и в качестве необязательного аргумента значение по умолчанию. Каждый раз, когда вызывается next(), она возвращает следующий элемент итератора, пока не останется ни одного элемента. В этот момент функция next возвращает значение по умолчанию (если ей было передано) или возникает исключение StopIterarion.

В этом уроке вы узнаете, в каких случаях можно использовать функцию next() как часть ваших программ на Python.

Давайте начнем!

Что делает next() в Python?

Функция Python next принимает два аргумента: первый — итератор, и он обязателен. Второй — значение по умолчанию, и он необязателен.

next(iterator[, default_value])

Каждый раз, когда вы передаете итератор следующей функции, вы получаете обратно следующий элемент итератора.

Например, давайте определим список Python, а затем создадим итератор с помощью функции iter().

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

Прежде чем тестировать следующую функцию, давайте рассмотрим разницу между типом, возвращаемым интерпретатором Python для списка и для итератора, связанного со списком.

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

А теперь давайте посмотрим, что мы получим, когда вызовем следующую функцию и передадим ей наш итератор:

>>> next(numbers_iterator)
1 

Вот что произойдет, если мы вызовем функцию next несколько раз, пока в итераторе не останется ни одного элемента.

>>> 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

Как только в итераторе больше нет элементов, интерпретатор Python вызывает исключение StopIteration.

Как вернуть значение по умолчанию из следующей функции Python

Если вы не хотите, чтобы Python вызывал исключение StopIteration при достижении конца итератора, вы также можете передать необязательное значение по умолчанию следующей функции.

Давайте возьмем тот же список, который мы использовали раньше, но на этот раз мы передадим значение по умолчанию следующей функции.

>>> 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' 

Как видите, при достижении конца итератора мы больше не получаем исключение, вместо этого мы возвращаем строку по умолчанию, переданную как необязательное значение следующей функции.

Другим вариантом может быть возврат None в качестве значения по умолчанию, если вы хотите легко программно проверить достижение конца итератора.

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

Как следующая функция связана с методом __next__?

Некоторые объекты Python предоставляют метод, называемый __next__.

Знаете ли вы, в чем разница между методом __next__ и функцией next()?

Когда вы вызываете функцию next() и передаете ей итератор, вызывается метод __next__ объекта итератора.

Интересно, можем ли мы вызвать метод __next__ итератора напрямую и получить тот же результат:

>>> 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 

Да, мы можем!

Таким образом, поведение метода __next__ такое же, как и у функции next().

Чтобы лучше понять, как это работает, давайте передадим в функцию next() список вместо того, чтобы передавать ей итератор.

>>> 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 

Интерпретатор Python вызывает исключение TypeError, поскольку список не является итератором и не реализует метод __next__.

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

Как видите, в списке нет атрибута с именем __next__, поскольку этот метод не реализован в списках.

Если мы выполним ту же проверку с итератором списка, то получим сведения о его методе __next__.

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

Это показывает, почему функцию next() можно применять к итераторам, но не к итерируемым объектам, таким как списки.

Функции Python Next и выражения генератора

Функцию next() также можно использовать с генераторами Python.

Давайте возьмем наш список чисел и создадим выражение-генератор для удвоения каждого числа в списке:

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

Теперь передадим этот генератор в функцию next() и посмотрим, что она вернет:

>>> 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 

Мы получаем от генератора ожидаемые значения, а интерпретатор Python вызывает исключение StopIteration, когда достигает конца генератора.

Как мы уже делали ранее с нашим итератором, мы можем подтвердить, что генератор также реализует метод __next__, который вызывается, когда генератор передается функции next():

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

В Python каждый генератор — это итератор. Они оба реализуют метод __next__.

Используйте next для получения первого элемента в итерируемом объекте, соответствующего условию

Допустим, у вас есть итерируемый объект, например кортеж, и вы хотите получить первый элемент в итерируемом объекте, который соответствует определенному условию.

Первый способ сделать это — использовать цикл for

Например, имея следующий кортеж, я хочу узнать первый элемент, больший 10:

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

С помощью цикла for мы бы сделали следующее:

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

Другой вариант — использовать функцию next() с выражением-генератором.

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

Что делать, если наше условие не соответствует ни одному элементу в генераторе?

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

В этом случае возникает исключение StopIteration.

Как мы видели в одном из разделов выше, мы также можем передать значение по умолчанию функции next(), чтобы избежать этого исключения.

Давайте сделаем это…

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

Обратите внимание, что первый параметр, переданный функции next(), является генератором, а второй параметр — значением по умолчанию.

Следующая функция Python, примененная к генератору с лямбда-условием

В предыдущем коде мы использовали функцию next() и генератор. Мы также можем использовать лямбда-функцию в качестве условия.

Учитывая тот же кортеж, который мы использовали ранее, давайте запишем выражение генератора с использованием лямбда-выражения:

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

Обратите внимание, как меняется способ записи условия if:

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

Это позволяет нам сделать условие if более общим.

Производительность цикла For по сравнению с функцией next

Используя функцию Python next(), мы можем воспроизвести то же поведение цикла for.

Мне интересно, какой из двух подходов самый быстрый.

Давайте создадим список из 100 000 элементов, используя функцию Python range.

numbers = list(range(100000))

Мы зафиксируем время начала и окончания каждой реализации, чтобы увидеть, сколько времени займет выполнение каждой реализации.

Цикл for

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 

Далее с помощью итератора

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

Цикл for работает быстрее, чем функция next(), использующая итератор.

Заключение

В этом уроке мы увидели, как можно использовать функцию Python next() в своих программах.

Вы также должны понимать, как работают итераторы и генераторы.

Итак, как вы будете использовать функцию next() в своем коде? 🙂

Автор

Фото аватара

Владимир Михайлов

Программист на Python с большим количеством опыта и разнообразных проектов.