Разберите ошибку: почему объект может поддерживать обход в цикле, но не поддерживать вызов next напрямую?
Потому что итерируемый объект и итератор — разные роли. Цикл сначала получает итератор через iter(...), а next(...) можно применять только к объекту, у которого есть метод __next__.
Например, список является итерируемым объектом, но сам не является итератором. Полученный из списка итератор уже хранит состояние обхода и поддерживает next.
Протокол итерации появился как единый способ последовательного обхода разных коллекций без привязки к их внутреннему устройству. Циклу не нужно знать, является ли источник списком, файлом, словарём или ленивым генератором.
Разделение ролей также позволяет создавать новый независимый обход одного источника. Итерируемый объект обычно описывает данные, а итератор — конкретное текущее положение внутри обхода.
Ошибка возникает, когда разработчик передаёт в next сам контейнер, считая, что любой объект, поддерживаемый циклом, автоматически является итератором. В результате появляется TypeError, хотя обычный цикл по тому же объекту работает.
Неверное понимание особенно опасно при проектировании API: можно случайно требовать от вызывающей стороны итератор вместо итерируемого объекта или передать одноразовый итератор туда, где ожидается возможность повторного обхода.
Итерируемый объект должен предоставлять метод __iter__, возвращающий итератор. Итератор должен предоставлять __next__, возвращающий следующий элемент и возбуждающий StopIteration после завершения. Обычно итератор также возвращает самого себя из __iter__.
Когда запускается цикл, Python концептуально выполняет iter(источник), а затем многократно вызывает next у полученного результата. Поэтому цикл скрывает промежуточное получение итератора, а прямой вызов next требует выполнить его явно.
В этом примере values — итерируемый список, а iterator — отдельный итератор с состоянием. Повторный вызов iter(values) обычно создаёт новый независимый итератор, тогда как повторный вызов iter(iterator) возвращает тот же уже продвинутый объект.
Итерируемый объект не обязан быть коллекцией: им может быть объект, который вычисляет элементы по запросу. Итератор, напротив, является одноразовым состоянием обхода: после StopIteration он обычно не возвращается к началу.
Сервис постранично читает большой набор записей из внешнего источника. Разработчик передал объект-источник в цикл, но затем попытался получить первую запись через next у самого источника и получил ошибку: источник был итерируемым объектом, а не итератором.
Рассматривались три варианта:
iter и передавать его потребителю: сохраняется потоковая обработка и явно фиксируется состояние обхода, но потребители должны учитывать одноразовость.Выбрали третий вариант, потому что набор был большим, а порядок чтения имел значение. В результате данные обрабатывались постепенно, без лишнего расхода памяти, а граница между источником и состоянием обхода стала явной.
iter несколько раз у одного объекта?Это зависит от объекта. Контейнер обычно возвращает новый независимый итератор при каждом вызове iter, поэтому два цикла могут начинать обход с начала. У самого итератора iter обычно возвращает тот же объект, поэтому второй потребитель продолжит уже изменённое состояние, а не получит новую последовательность.
__iter__?Для полноценного соответствия протоколу итерации — да: итератор должен возвращать себя из __iter__. Это позволяет использовать его там, где ожидается итерируемый объект, включая вложенные циклы и функции, принимающие iterable. Наличие только __next__ может позволить прямой вызов next, но не делает объект корректным итерируемым объектом во всех контекстах.
StopIteration при повторном вызове next?Корректный исчерпаемый итератор должен продолжать возбуждать StopIteration, а не самопроизвольно начинать обход заново. Цикл использует это исключение как сигнал завершения и скрывает его от вызывающего кода. Если требуется новый обход, обычно получают новый итератор от исходного итерируемого объекта; для уже исчерпанного одноразового итератора восстановить начальное состояние стандартный протокол не позволяет.