Поведение итератора в классе: почему next(obj) вызывает TypeError

Поведение итератора в классе: почему next(obj) вызывает TypeError

@python_quiz

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

Код из квиза (сжатое создание класса через type) выглядит так:

python

Коротко: этот код приведёт к ошибке TypeError при вызове next(it1). Разберёмся, почему.

Что делает код

  • В __init__ в поле data сохраняется iter(d) — то есть уже созданный итератор по списку [1, 2].
  • Метод __iter__ возвращает self.data — тот самый внутренний итератор.
  • Экземпляр класса не имеет метода __next__ (или next в Python 2), то есть сам по себе объект не реализует протокол итератора.

Почему возникает TypeError

Функция next(obj) ожидает, что obj реализует метод __next__ (т. е. сам является итератором). Она не вызывает __iter__ перед попыткой получить следующий элемент. Поскольку экземпляр класса не имеет __next__, вызов next(it1) приводит к TypeError: объект не является итератором.

В то же время вызов iter(it1) работает: он вызывает __iter__ и возвращает внутренний итератор (self.data). Поэтому переменная it2 становится настоящим итератором, и list(it2) корректно вернёт оставшиеся элементы.

Итог: попытка вызвать next(it1) приведёт к TypeError, потому что объект предоставляет только __iter__, но не __next__.

Протоколы Python: iterable vs iterator

Коротко:

  • Iterable — объект с методом __iter__(), который возвращает итератор.
  • Iterator — объект с методами __iter__() (обычно возвращает self) и __next__(), который возвращает следующий элемент.

Если объект реализует только __iter__ и возвращает отдельный итератор, сам объект — iterable, но не iterator. next(obj) требует именно iterator.

Как исправить (варианты)

1) Сделать объект самим итератором (реализовать __next__), делегируя внутреннему итератору:

python

Пояснение: теперь it1 является итератором; iter(it1) возвращает тот же объект. next(it1) возвращает первый элемент, а list(it2) (it2 — то же самое) вернёт оставшийся элемент.

2) Если вы хотите, чтобы каждый вызов iter(obj) давал новый итератор (обычное поведение для контейнеров), храните исходные данные (не итератор) и возвращайте fresh-итератор из __iter__:

python

Пояснение: теперь iterable возвращает новый итератор при каждом вызове iter(...). Экземпляр не является iterator-ом, поэтому next(it1) по-прежнему некорректен.

Дополнительная деталь: хранение iter(d) в __init__

В исходном коде в __init__ сохранялся iter(d). Это значит, что вы заранее создали одноразовый итератор. Если __iter__ возвращает именно этот объект, несколько вызовов iter(obj) вернут один и тот же итератор (необычное поведение для контейнероподобных объектов). Это полезно иногда (например, объект оборачивает внешний поток), но важно понимать, что итератор "истощается" по мере прохода.

Вывод

  • Наличие только __iter__ не делает объект итератором. next(obj) требует наличия __next__.
  • В исходном примере next(it1) вызывает TypeError, потому что экземпляр не реализует __next__.
  • Исправление зависит от желаемого поведения: сделать объект итератором (реализовать __next__) или хранить контейнер и возвращать новый итератор из __iter__.

Если нужно, могу показать ещё примеры с генераторами или объяснить, когда полезно возвращать один и тот же внутренний итератор, а когда — новый для каждого __iter__.

Report Page