Перейти к содержимому
Теория · Модули, файлы и генераторы · урок 7 из 12

Итераторы

Представьте, что вы читаете книгу по вечерам. Закончив, вы кладёте между страницами закладку. На следующий вечер вы открываете книгу на закладке и читаете дальше, а не с первой страницы.

Цикл for обходит списки, строки, словари, range и файлы, и у всех них разное устройство. Брать значения по одному ему помогает итератор — объект, который помнит, на каком месте остановился обход. Итератор похож на закладку: сама книга — это список, а закладка хранит только место, где вы остановились.

Итератор даёт функция iter, а следующее значение — функция next:

Python
nums = [10, 20]
it = iter(nums)
print(next(it))  # 10
print(next(it))  # 20
print(next(it, "end"))  # end

Давайте разберём пример по строкам:

  1. iter(nums) кладёт закладку перед первым элементом списка и возвращает итератор it.
  2. Первый next(it) возвращает 10 и сдвигает закладку на один элемент.
  3. Второй next(it) возвращает 20: закладка помнит, где остановилась.
  4. Элементы кончились, и next(it, "end") возвращает свой второй аргумент.

Без второго аргумента next(it) на исчерпанном итераторе вызывает исключение StopIteration. Назад итератор не ходит и своей длины не знает: у него есть только следующий элемент. В этом аналогия и заканчивается: бумажную закладку можно переложить на начало, а итератор умеет двигаться только вперёд.

Что делает for

Цикл for ch in "ab": выполняет те же шаги, что и эта программа:

Python
it = iter("ab")
while True:
    try:
        ch = next(it)
    except StopIteration:
        break
    print(ch, end=".")  # a.b.

Разберём, что здесь делает цикл for:

  1. Он вызывает iter один раз, перед первой итерацией.
  2. Перед каждой итерацией он вызывает next и записывает результат в переменную цикла.
  3. Когда next вызывает StopIteration, цикл перехватывает исключение и завершается.

StopIteration для цикла не ошибка, а сигнал конца. Итератор словаря выдаёт ключи, поэтому for k in d перебирает ключи словаря d.

Итерируемый объект и итератор

Этот вопрос часто задают на собеседовании: «Чем итерируемый объект отличается от итератора?» Разберём оба понятия.

Итерируемый объект — книга

Итерируемый объект — всё, из чего iter умеет сделать итератор: список, строка, словарь, range. Это книга. Её можно читать сколько угодно раз, потому что каждый вызов iter кладёт новую закладку в начало: iter(nums) is iter(nums) даёт False.

Итератор — закладка

Итератор — это сама закладка, и проходится он один раз: исчерпанный, он остаётся пустым. zip и enumerate возвращают итераторы, а не списки, и файловый объект — тоже итератор. Возьмём pairs = zip("ab", [1, 2]):

  • первый list(pairs) даёт [('a', 1), ('b', 2)];
  • второй list(pairs) даёт [], потому что закладка уже в конце.

Итератор тоже итерируемый: iter(pairs) возвращает сам pairs. Поэтому for по итератору продолжает с того места, где обход остановился.

Частая ошибка

next вызывают прямо от списка:

Python
nums = [10, 20]
print(next(nums))  # ✗
TEXT
TypeError: 'list' object is not an iterator

Список — итерируемый объект, но не итератор: позиции обхода он не хранит. Тот же TypeError дают строка, словарь и range: все они итерируемые объекты, но не итераторы. Сначала получите итератор: next(iter(nums)) даёт 10.

Главное

  • iter даёт итератор, а next — его следующий элемент; в конце next вызывает StopIteration.
  • next(it, default) вместо исключения возвращает default.
  • for сам вызывает iter и next и завершается на StopIteration.
  • Итерируемый объект можно обходить много раз, а итератор — только один.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом