К этому моменту мы много раз использовали цикл for с разными типами данных. Списки, словари, кортежи, строки, файлы — всё это можно обрабатывать с помощью цикла:

for element in [1, 2, 3]:
    print(element)

for element in (1, 2, 3):
    print(element)

for key in {'one':1, 'two':2}:
    print(key)

for char in "123":
    print(char)

for line in open('myfile.txt'):
    print(line, end='')

Как же это всё работает? 🤔 Все типы данных, которые позволяют перебирать себя с помощью цикла, используют итераторы.

<aside> 💡 Итератор — это объект, который позволяет проходить по элементам, используя встроенные средства языка.

</aside>

Под капотом для работы цикла for Python использует две встроенные функции: iter() и next(). Первая функция получает итератор, вторая проходит по этому итератору:

data = [1, 2, 3]       # Создаём список из трёх элементов

iterator = iter(data)  # Берём его итератор

print(next(iterator))  # Берём первое число из итератора, выведется 1
print(next(iterator))  # Берём второе число из итератора, выведется 2
print(next(iterator))  # Берём третье число из итератора, выведется 3

Этот код можно переписать с помощью цикла while так, чтобы в нём вызывалась функция next():

data = [1, 2, 3]           # Создаём список из трёх элементов

iterator = iter(data)      # Берём его итератор

while True:
    print(next(iterator))  # Берём очередной элемент из итератора

Однако если запустить этот код, мы получим не совсем то, что ожидали увидеть:

1
2
3
Traceback (most recent call last):
  File "main.py", line 6, in <module>
    print(next(iterator))  # Берём очередной элемент из итератора
StopIteration

Здесь мы получаем последний кусок пазла: если функция next() вызовется, когда итератор дошёл до конца, то Python выбросит исключение StopIteration, которое как раз и говорит о том, что итерироваться больше не нужно.

С учётом этого дополним нашу программу:

data = [1, 2, 3]               # Создаём список из трёх элементов

iterator = iter(data)          # Берём его итератор

while True:
    try:
        print(next(iterator))  # Берём очередной элемент из итератора
    except StopIteration:      # Когда получаем исключение StopIteration,
        break                  # то выходим из цикла

Вот так любой цикл for можно развернуть в цикл while. Само по себе это немного бесполезное умение, однако здесь мы увидели механизмы, которые позволят нам чуть дальше создать свой класс, объекты которого смогут итерироваться в цикле.