Поведение итераторов в Python: когда __iter__ возвращает self

Поведение итераторов в Python: когда __iter__ возвращает self

@python_quiz

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

Коротко: код создает класс-итератор, затем берет из него два «итератора» и вызывает next через оба — важно понять, разделяют ли они состояние.

Условие — код из квиза

python

Что происходит (итераторный протокол)

  • В Python объект считается итерируемым, если у него есть метод __iter__, который возвращает итератор.
  • Итератор — объект с методом __next__, который при каждом вызове возвращает следующее значение.
  • Частый паттерн: объект-итератор возвращает сам себя в __iter__ (self), то есть объект и есть итератор. Тогда несколько переменных, получивших итератор из одного и того же объекта, будут ссылаться на один и тот же объект и разделять его внутреннее состояние.

В данном коде __iter__ возвращает self, поэтому c1 и c2 указывают на один и тот же объект.

Пошаговое выполнение

Начальное состояние: c1.x = 0

  1. next(c1) — вызывает __next__ у того же объекта:

- x увеличивается до 1, возвращается 1

  1. next(c2) — c2 это тот же объект, x = 1:

- x увеличивается до 2, возвращается 2

  1. 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 возвращает новый итератор):

python

Тут a и b — разные объекты-итераторы, поэтому они имеют собственные счётчики.

2) Использовать генератор для создания нового итератора при каждом вызове iter:

python

Заключение

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

python

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

Report Page