Короткий ответ
Класс-итератор реализует __iter__ (обычно возвращает self) и __next__ (вычисляет и возвращает следующее значение при каждом вызове); об окончании последовательности он сигнализирует, возбуждая исключение StopIteration внутри __next__, которое цикл for автоматически перехватывает и завершает итерацию.
Как это работает подробнее
Протокол итератора требует двух методов: __iter__(self), возвращающий объект-итератор (часто self, если класс одновременно итерируемый и итератор), и __next__(self), возвращающий очередное значение или возбуждающий StopIteration, когда значений больше нет. Конструкция for x in obj: под капотом вызывает iter(obj) один раз, затем повторяет next(it) в цикле, автоматически останавливаясь при перехвате StopIteration без вывода ошибки пользователю.
Пример кода
example.py
class Squares:
def __init__(self, n):
self.n = n
def __iter__(self):
return SquaresIterator(self.n)
class SquaresIterator:
def __init__(self, n):
self.n = n
self.current = 1
def __iter__(self):
return self
def __next__(self):
if self.current > self.n:
raise StopIteration
value = self.current ** 2
self.current += 1
return value
squares = Squares(4)
print(list(squares)) # [1, 4, 9, 16]
print(list(squares)) # [1, 4, 9, 16] — можно пройти снова, т.к. __iter__ создаёт новый итераторЧто использовать на практике
- Практика. Такой подход используют, когда нужен контроль над состоянием итерации (например, итератор с ленивой подгрузкой страниц из API или БД), а не просто требуется перебрать готовую коллекцию, для которой было бы достаточно генератора с
yield. Часто разделяют класс-контейнер (реализует только__iter__, создающий новый итератор на каждый вызов) и отдельный класс-итератор (хранит текущую позицию) — это позволяет проходить по одному контейнеру несколько раз параллельно.
Подводные камни
- Риски. Делают контейнер и итератор одним и тем же объектом без разделения состояния, из-за чего повторный
forпо уже пройденному объекту не выдаёт ничего (итератор уже исчерпан) — для повторной итерации нужен новый объект-итератор. Также забывают возбуждатьStopIterationи получают бесконечный циклfor.