Как получить следующий элемент итератора в Python
Что проверяют
Знаете ли вы протокол итерации: разницу между итерируемым объектом и итератором, iter(), next() и StopIteration. Следом обычно просят написать свой итератор или генератор и спрашивают, почему итератор нельзя пройти дважды.
Как отвечать
Ответ в одну строку: next(it). Дальше разворачиваете:
- итерируемое и итератор: у списка, строки, словаря есть
__iter__. Итератор вдобавок имеет__next__и из__iter__возвращает сам себя. Поэтомуnext([1, 2])падает сTypeError, сначала нуженiter(); - конец: когда элементы кончились,
__next__бросаетStopIteration. Циклforловит его сам, а в ручном коде удобнее значение по умолчанию:next(it, None); next()против__next__(): делают одно и то же, но встроенная функция принимает default и считается нормальным стилем, а прямой вызов__next__()в прикладном коде выглядит странно;- одноразовость: исчерпанный итератор остаётся пустым, второй
list(gen)вернёт[]; - генераторы: функция с
yieldвозвращает итератор, это самый короткий способ написать свой.StopIteration, вырвавшийся изнутри генератора, превращается вRuntimeError(PEP 479, по умолчанию с Python 3.7); - полезные приёмы:
next(x for x in items if x.ok)находит первый подходящий элемент без полного прохода,itertools.islice(it, 5)берёт первые пять,itertools.teeпозволяет подсмотреть вперёд ценой буфера.
Для асинхронного кода есть пара aiter() и anext(), встроенных с Python 3.10.
Где спотыкаются
Пишут while True с try/except StopIteration вместо обычного for. Работает, но выдаёт незнание языка.
Вызывают next() на генераторе, который уже частично прошёл for, и удивляются пропавшим элементам: итератор общий, позиция одна.
Используют next(x for x in items if ...) без значения по умолчанию. Если подходящего элемента нет, вылетает StopIteration, а внутри другого генератора он превращается в непонятный RuntimeError.