Шаг 145.
Python: тонкости программирования.
Циклы и итерации. Красивые итераторы. Бесконечное повторение

    На этом шаге мы реализуем первый итератор.

    Начнем с того, что напишем класс, который демонстрирует скелетный протокол итератора. Используемый здесь пример, возможно, по виду отличается от примеров, которые вы видели в других пособиях по итераторам, но наберитесь терпения. Надеемся, что в таком виде он предоставит вам более компетентное понимание того, как итераторы работают в Python.

    В последующих нескольких абзацах мы собираемся реализовать класс, который мы назовем повторителем Repeater, итерации по которому можно выполнять в цикле for ... in следующим образом:

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

    Как следует из его имени, экземпляры класса Repeater при его итеративном обходе будут неизменно возвращать единственное значение. Поэтому приведенный выше пример кода будет бесконечно печатать в консоли строковый литерал 'Привет'.

    Начиная реализацию, мы, прежде всего, определим и конкретизируем класс Repeater:

class Repeater:
    def __init__(self, value):
        self.value = value

    def __iter__(self):
        return RepeaterIterator(self)

    При первоначальном осмотре класс Repeater похож на заурядный класс Python. Но обратите внимание, что он также включает метод __iter__().

    Что за объект RepeaterIterator мы создаем и возвращаем из дандер-метода __iter__()? Это вспомогательный класс, который нам нужно определить, чтобы заработал наш пример итераций в цикле for ... in:

class RepeaterIterator:
    def __init__(self, source):
        self.source = source

    def __next__(self):
        return self.source.value

    И снова, RepeaterIterator похож на прямолинейный класс Python, но, возможно, вам стоит принять во внимание следующие две вещи:

  1. В методе __init__() мы связываем каждый экземпляр класса RepeaterIterator с объектом Repeater, который его создал. Благодаря этому мы можем держаться за "исходный" объект, итерации по которому выполняются.

  2. В RepeaterIterator.__next__() мы залезаем назад в "исходный" экземпляр класса Repeater и возвращаем связанное с ним значение.

    В этом примере кода Repeater и RepeaterIterator работают вместе, чтобы поддерживать протокол итератора Python. Два определенных нами дандер-метода, __init__() и __next__(), являются центральными в создании итерируемого объекта Python.

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

    Давайте подтвердим, что эта конфигурация с двумя классами действительно сделала объекты класса Repeater совместимыми с итерацией в цикле for ... in. Для этого мы сначала создадим экземпляр класса Repeater, который будет бесконечно возвращать строковый литерал 'Привет':

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

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

>>> for item in repeater: print(item)

    Точно! Вы увидите, как на экране будет напечатано 'Привет'... много раз. Объект repeater продолжает возвращать то же самое строковое значение, и этот цикл никогда не завершится. Наша небольшая программа обречена печатать в консоли 'Привет' до бесконечности:

Привет
Привет
Привет
Привет
Привет
.   .   .   .

    И тем не менее примите поздравления - вы только что написали работающий итератор на Python и применили его в цикле for ... in. Этот цикл все еще не может завершиться. но пока что все идет неплохо!

    Теперь мы разделим этот пример на части, чтобы понять, как методы __init__() и __next__() работают вместе, делая объект Python итерируемым.

    Профессиональный совет: если вы выполнили предыдущий пример в сеансе Python REPL или в терминале и хотите его остановить, нажмите сочетание клавиш Ctrl + C несколько раз, чтобы выйти из бесконечного цикла.

    На следующем шаге мы рассмотрим как циклы for ... in работают в Python.




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