Поведение итератора в классе: почему next(obj) вызывает TypeError
@python_quizРазберем этот квиз
Код из квиза (сжатое создание класса через type) выглядит так:

Коротко: этот код приведёт к ошибке 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__), делегируя внутреннему итератору:

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

Пояснение: теперь 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__.