Поведение итераторов в Python: когда __iter__ возвращает self
@python_quizРазберем этот квиз
Коротко: код создает класс-итератор, затем берет из него два «итератора» и вызывает next через оба — важно понять, разделяют ли они состояние.
Условие — код из квиза

Что происходит (итераторный протокол)
- В Python объект считается итерируемым, если у него есть метод
__iter__, который возвращает итератор. - Итератор — объект с методом
__next__, который при каждом вызове возвращает следующее значение. - Частый паттерн: объект-итератор возвращает сам себя в
__iter__(self), то есть объект и есть итератор. Тогда несколько переменных, получивших итератор из одного и того же объекта, будут ссылаться на один и тот же объект и разделять его внутреннее состояние.
В данном коде __iter__ возвращает self, поэтому c1 и c2 указывают на один и тот же объект.
Пошаговое выполнение
Начальное состояние: c1.x = 0
- next(c1) — вызывает
__next__у того же объекта:
- x увеличивается до 1, возвращается 1
- next(c2) — c2 это тот же объект,
x = 1:
- x увеличивается до 2, возвращается 2
- next(c1) — снова тот же объект,
x = 2:
- x увеличивается до 3, возвращается 3
Итоговая печать: 1 2 3
Анализ вариантов ответов (логика почему одни варианты неверны)
- "1 1 1"
- Такое поведение означало бы, что каждый вызов next возвращает 1 независимо от предыдущих — это противоречит инкременту self.x в __next__. Невозможно при данной реализации.
- "1 2 3"
- Соответствует описанному шаг-за-шагом поведению: один общий счётчик между c1 и c2.
- "1 2 2"
- Такое могло бы случиться, если бы, например, __iter__ возвращал разные объекты, у которых копия состояния приводила к повторному значению. Но при текущей реализации __iter__ возвращает self, а не новый объект, поэтому после второго вызова значение увеличивается до 2, а третий вызов даст 3, а не 2.
- "Ошибка
TypeErrorпри вызове iter(c1)"
- TypeError возник бы, если бы объект не был итерируемым (нет __iter__) или __iter__ вернул бы что-то невалидное. Здесь __iter__ присутствует и возвращает валидный итератор (self), поэтому ошибка не возникает.
Как изменить поведение (несколько примеров)
1) Сделать итератор независимым (каждый iter возвращает новый итератор):

Тут a и b — разные объекты-итераторы, поэтому они имеют собственные счётчики.
2) Использовать генератор для создания нового итератора при каждом вызове iter:

Заключение
В оригинальном коде объект является итерируемым и одновременно итератором (__iter__ возвращает self), поэтому все переменные, получившие итератор из него, разделяют одно и то же внутреннее состояние. Это даёт последовательный вывод:

Если нужно, чтобы каждый вызов iter() давал независимый итератор, __iter__ должен возвращать новый объект-итератор (или генератор), а не self.