На этом шаге мы рассмотрим реализацию итератора одним классом.
До этого момента наш пример итератора состоял из двух отдельных классов, Repeater и RepeaterIterator. Они соответствовали непосредственно двум фазам, используемым в протоколе итератора Python: сначала подготовке и получению объекта-итератора через вызов функции iter(), а затем неоднократной доставке из него значений через вызов функции next().
Во многих случаях обе эти функциональные обязанности можно поручить на одому-единственному классу. Это позволит сократить объем программного кода, необходимого для написания итератора, основанного на классах.
Мы решили этого не делать с первым примером в данном разделе, потому что это внесло бы путаницу в чистоту ментальной модели в основе протокола итератора. Но теперь, когда вы увидели, как писать итератор на основе классов более долгим и более сложным способом, давайте потратим еще минуту, чтобы упростить то, что у нас есть на данный момент.
Помните, почему нам вновь потребовался класс RepeaterIterator? Он был нужен, чтобы принять метод __next__() для доставки новых значений из итератора. Но место определения метода __next__() вовсе не имеет никакого значения. В протоколе итератора имеет значение только то, что метод __iter__() возвращает любой объект с определенным на нем методом __next__().
Поэтому идея такая: RepeaterIterator без конца возвращает одинаковое значение, и он не должен отслеживать никакое внутреннее состояние.
Что, если вместо этого добавить метод __next__() непосредственно в класс Repeater?
Тем самым мы смогли бы целиком избавиться от RepeaterIterator и реализовать итерируемый объект при помощи одного-единственного класса Python. Давайте попробуем! Наш пример с новым и упрощенным итератором выглядит так:
>>> class Repeater: def __init__(self, value): self.value = value def __iter__(self): return self def __next__(self): return self.value
Мы только что перешли от двух отдельных классов и десяти строк кода всего к одному классу и семи строкам кода. Наша упрощенная реализация по-прежнему без проблем поддерживает протокол итератора:
>>> repeater = Repeater('Привет') >>> for item in repeater: print(item) Привет Привет Привет . . . .
В подобной оптимизации итератора на основе класса часто есть смысл. По сути, большинство пособий Python по итераторам начинается именно так. Но объяснять итераторы одним-единственным классом с самого начала - значит скрывать основные принципы протокола итератора и по этой причине еще больше затруднять его понимание.
На следующем шаге мы рассмотрим ограничение выполнения итератора.