Шаг 146.
Python: тонкости программирования.
Циклы и итерации. Красивые итераторы. Как циклы for … in работают в Python?

    На этом шаге мы рассмотрим принцип работы этого цикла.

    На данном этапе у нас есть класс Repeater, который, несомненно, поддерживает протокол итератора, и мы просто выполнили цикл for ... in, чтобы это доказать:

repeater = Repeater('Привет') 
for item in repeater: 
    print(item)

    Итак, что же этот цикл for ... in в действительности делает за кадром? Как он контактирует с объектом repeater, чтобы доставать из него новые элементы?

    Чтобы рассеять часть этого "волшебства", мы можем расширить цикл в слегка удлиненном фрагменте кода, который дает тот же самый результат:

repeater = Repeater('Привет')
iterator = repeater.__iter__()
while True:
    item = iterator.__next__()
    print(item)

    Как видите, конструкция for ... in была всего лишь синтаксическим сахаром для простого цикла while:

    Если вы когда-либо работали с курсорами базы данных (database cursors), то эта ментальная модель будет выглядеть похожей: мы сначала инициализируем курсор и готовим его к чтению, а затем можем доставлять из него данные, один элемент за другим, в локальные переменные в нужном объеме.

    Поскольку "в активном состоянии" никогда не находится более одного элемента, этот подход чрезвычайно эффективен с точки зрения потребляемой оперативной памяти. Наш класс Repeater обеспечивает бесконечную последовательность элементов, и мы можем без проблем выполнять по нему итерации. Имитация того же самого при помощи списка Python list была бы невозможной - прежде всего, нет никакой возможности создать список с бесконечным количеством элементов. И это превращает итераторы в очень мощную концепцию.

    Говоря более абстрактно, итераторы обеспечивают единый интерфейс, который позволяет вам обрабатывать каждый элемент контейнера, оставаясь полностью изолированным от внутренней структуры последнего.

    Имеете ли вы дело со списком элементов, словарем, бесконечной последовательностью, например такой, которая обеспечивается нашим классом Repeater, или другим типом последовательности - все это просто детали реализации. Эти объекты все до единого можно проходить таким же образом при помощи мощных возможностей итераторов.

    Как вы убедились, в Python нет ничего особенного в циклах for ... in. Если вы заглянете за кулисы, то увидите, что все сводится к вызову правильных дандер-методов в нужное время.

    На самом деле в сеансе интерпретатора Python можно вручную "эмулировать" то, как цикл использует протокол итератора:

>>> repeater = Repeater('Привет')
>>> iterator = iter(repeater)
>>> next(iterator)
Привет
>>> next(iterator)
Привет
>>> next(iterator)
Привет

    Этот фрагмент кода дает тот же самый результат - бесконечный поток приветствий. Всякий раз, когда вы вызываете next(), итератор снова выдает то же самое приветствие.

    Между прочим, здесь мы воспользовались возможностью замены вызовов __ iter__() и __next__() на вызовы встроенных в Python функций iter() и next().

    На внутреннем уровне эти встроенные функции вызывают те же самые дандер-методы, но они делают программный код немного симпатичнее и более удобочитаемым, предоставляя протоколу итератора чистый "фасад".

    Python предлагает эти фасады также и для другой функциональности. Например, len(x) является краткой формой для вызова x.__len__(). Точно так же вызов функции iter(x) вызывает метод x.__iter__(), а вызов функции next(x) вызывает метод x.__next__(). .

    В целом неплохая идея использовать встроенные фасадные функции, вместо того чтобы непосредственно обращаться к дандер-методам, реализующим протокол итератора. Это намного упрощает восприятие исходного кода.

    На следующем шаге мы рассмотрим более простой класс-итератор.




Предыдущий шаг Содержание Следующий шаг