Для чего используется метод __next__ в Python
Что проверяют
Понимаете ли вы протокол итерации, на котором держатся for, генераторы, zip и распаковка. Почти всегда следом просят объяснить разницу между итерируемым объектом и итератором или написать свой итератор на доске.
Как отвечать
__next__ возвращает следующий элемент итератора, а когда элементы кончились, выбрасывает StopIteration. Напрямую его обычно не вызывают: встроенная next(it) вызывает его за вас, а next(it, default) вместо исключения вернёт значение по умолчанию.
Покажите, что происходит внутри for x in obj:
- интерпретатор вызывает
iter(obj), то естьobj.__iter__(), и получает итератор; - на каждом шаге вызывает у итератора
__next__()и кладёт результат вx; StopIterationперехватывается молча, и цикл завершается.
Отсюда различие двух ролей:
- итерируемый объект (
list,dict,str): по запросу выдаёт новый итератор через__iter__, поэтому по нему можно пройти сколько угодно раз; - итератор: хранит позицию, реализует
__next__, а его__iter__возвращаетself. Он одноразовый: после исчерпания второй проход пуст. По протоколу, раз выбросивStopIteration, он обязан выбрасывать его и дальше.
Руками __next__ пишут редко. Генератор с yield реализует протокол сам, и почти всегда он короче и надёжнее класса. С Python 3.7 (PEP 479) StopIteration, вылетевший внутри генератора, превращается в RuntimeError, чтобы цикл не обрывался незаметно. Асинхронный аналог это __anext__ и async for.
Где спотыкаются
Делают коллекцию собственным итератором: __iter__ возвращает self, а позиция хранится в самом объекте. Второй for по тому же объекту ничего не выдаёт, а вложенные циклы ломают друг друга.
Забывают raise StopIteration и в конце возвращают None. Цикл становится бесконечным, а list(obj) съедает всю память.
Удивляются, что повторный list(gen) возвращает пустой список. Генератор тоже итератор и тоже исчерпывается.