Один объект — два итератора: когда срабатывает StopIteration

Один объект — два итератора: когда срабатывает StopIteration

@python_quiz

Разберем этот квиз

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

Условие (сокращенно)

Код:

python

Внимание на две строки: it2 = iter(it1) и print(...) с тремя аргументами, которые будут вычисляться по очереди.

Пошаговый разбор

  1. Создается объект Once(10). Его __iter__ возвращает сам объект, то есть объект является и итерируемым, и итератором.
  2. it1 = iter(Once(10)) — итератор it1 ссылается на этот объект.
  3. it2 = iter(it1) — вызывается __iter__ у it1, который возвращает self, таким образом it2 — тот же самый объект (it2 is it1).
  4. При вызове print аргументы вычисляются слева направо:

- Сначала выполняется next(it1): объект ещё не использован, возвращается 10, флаг used устанавливается в True.
- Затем выполняется next(it2): это тот же объект, но теперь used == True, поэтому __next__ поднимает StopIteration.

  1. Исключение возникает во время вычисления аргумента и приводит к прерыванию вызова print — ничего не печатается, программа прерывается с StopIteration.

Важно: порядок вычисления аргументов в Python — слева направо, поэтому исключение возникает до вывода.

Демонстрация и проверка

Покажем, что it1 и it2 — один объект:

python

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

python

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

python

Или реализовать __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, если нужно "раздвоить" поток значений.

Report Page