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

    На этом шаге мы рассмотрим реализацию итератора одним классом.

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

    На следующем шаге мы рассмотрим ограничение выполнения итератора.




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