Функция 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() в своем коде? 🙂