Итераторы и протокол итерации

Фундаментальный вопрос: что на самом деле делает for и чем итератор отличается от итерируемого.

Итерируемый (iterable) объект умеет отдать итератор через __iter__. Итератор (iterator) умеет выдавать следующий элемент через __next__ и заканчивается исключением StopIteration.

Вопрос: как работает цикл for?

Чёткий ответ. for x in obj вызывает iter(obj), получает итератор и в цикле дёргает next(...), пока не прилетит StopIteration. Список — iterable, но не итератор: итератор для него создаётся отдельно.

nums = [10, 20, 30]
it = iter(nums)            # получили итератор
print(next(it))
print(next(it))
print(next(it))
try:
    next(it)              # элементы кончились
except StopIteration:
    print("StopIteration — конец")

Вывод:

10
20
30
StopIteration — конец

Свой итератор: реализуем протокол

Чтобы объект работал в for, достаточно реализовать __iter__ и __next__.

class Countdown:
    def __init__(self, start):
        self.current = start

    def __iter__(self):
        return self                  # сам объект — итератор

    def __next__(self):
        if self.current <= 0:
            raise StopIteration
        self.current -= 1
        return self.current + 1

for n in Countdown(3):
    print(n)

Вывод:

3
2
1

Итератор одноразовый

Важная деталь: итератор «истощается». Пройдя по нему один раз, второй раз вы получите пустоту — в отличие от списка, по которому можно ходить много раз.

nums = [1, 2, 3]
it = iter(nums)
print("первый проход:", list(it))
print("второй проход:", list(it))   # уже пусто
print("список снова:", list(nums))  # а список — сколько угодно раз

Вывод:

первый проход: [1, 2, 3]
второй проход: []
список снова: [1, 2, 3]

Итог

  • iterable отдаёт итератор через __iter__; iterator выдаёт элементы через __next__.
  • for = iter() + повторный next() до StopIteration.
  • Итератор одноразовый: после прохода он пуст.
Проверьте себя
1. Какие два метода образуют протокол итератора?
A__init__ и __call__
B__iter__ и __next__
C__len__ и __getitem__
D__enter__ и __exit__
2. Чем завершается итерация?
Areturn None
BИсключением StopIteration
CПустым списком
DБесконечным циклом
3. Что вернёт второй list(it) для итератора, по которому уже прошли?
AТе же элементы снова
BПустой список []
CОшибку
DNone
Поддержать проект