На этом шаге мы реализуем первый итератор.
Начнем с того, что напишем класс, который демонстрирует скелетный протокол итератора. Используемый здесь пример, возможно, по виду отличается от примеров, которые вы видели в других пособиях по итераторам, но наберитесь терпения. Надеемся, что в таком виде он предоставит вам более компетентное понимание того, как итераторы работают в 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, но, возможно, вам стоит принять во внимание следующие две вещи:
В этом примере кода 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.