Один объект — два итератора: когда срабатывает StopIteration
@python_quizРазберем этот квиз
Коротко: в данном примере оба "итератора" указывают на один и тот же объект. Вызовы аргументов функции вычисляются слева направо, поэтому второй вызов next() встречает уже исчерпанный итератор и поднимает StopIteration до того, как print что‑то напечатает.
Условие (сокращенно)
Код:

Внимание на две строки: it2 = iter(it1) и print(...) с тремя аргументами, которые будут вычисляться по очереди.
Пошаговый разбор
- Создается объект Once(10). Его
__iter__возвращает сам объект, то есть объект является и итерируемым, и итератором. it1 = iter(Once(10))— итератор it1 ссылается на этот объект.it2 = iter(it1)— вызывается__iter__у it1, который возвращаетself, таким образом it2 — тот же самый объект (it2 is it1).- При вызове print аргументы вычисляются слева направо:
- Сначала выполняется next(it1): объект ещё не использован, возвращается 10, флаг used устанавливается в True.
- Затем выполняется next(it2): это тот же объект, но теперь used == True, поэтому __next__ поднимает StopIteration.
- Исключение возникает во время вычисления аргумента и приводит к прерыванию вызова print — ничего не печатается, программа прерывается с StopIteration.
Важно: порядок вычисления аргументов в Python — слева направо, поэтому исключение возникает до вывода.
Демонстрация и проверка
Покажем, что it1 и it2 — один объект:

Оригинальный код приведет к исключению. Чтобы поймать его явно:

Если хочется получить два независимых значения 10 и напечатать пустой список после — нужно иметь два независимых итератора (например, два отдельных объекта Once):

Или реализовать __iter__ так, чтобы он возвращал новый итератор при каждом вызове (как это делает, например, список).
Анализ возможных вариантов ответов (логика)
- Вариант с выводом "10 10 [10, 10]" предполагает, что каждый next() возвращает 10, а list(it1) ещё вернёт два значения — это невозможно для данного класса Once, который отдаёт значение только один раз.
- Вариант "10 10 [10]" тоже не согласуется с поведением: если оба next() вернули 10, то итератор уже исчерпан, и list(it1) вернёт [].
- Вариант "10 10 []" возможен, но только если it1 и it2 были независимы (два разных итератора). В текущем коде it1 и it2 — один объект, так что это не случится.
- Фактическое поведение — возбуждение StopIteration во время вычисления аргументов перед вызовом print, поэтому ничего не печатается, а программа прерывается (если исключение не перехвачено).
Выводы и рекомендации
- Не предполагайте, что вызов iter(obj) всегда создаёт новый независимый итератор. Поведение зависит от реализации
__iter__. - Помните, что функции в Python вычисляют аргументы слева направо — исключения в аргументах останавливают вызов до входа в функцию.
- Если нужно несколько независимых проходов по данным, используйте объекты, у которых
__iter__возвращает новый итератор (или явно создавайте независимые итераторы/копии данных).
Небольшая практическая проверка/хаки: чтобы избежать неожиданных StopIteration при передаче одного итератора в разные места, явно проверяйте identity итераторов (it1 is it2) или используйте itertools.tee, если нужно "раздвоить" поток значений.