Шаг 152.
Python: тонкости программирования. Циклы и итерации. Генераторы - это упрощенные итераторы. Бесконечные генераторы

    На этом шаге мы рассмотрим создание таких генераторов с использованием конструкции yield.

    Давайте начнем с того, что посмотрим еще раз на пример с классом Repeater, который мы уже использовали, чтобы познакомить вас с идеей итераторов. В нем реализована итеративная обработка бесконечной последовательности значений на основе класса. Вот так этот класс выглядел в своей второй (упрощенной) версии:

>>> class Repeater:
	def __init__(self, value):
		self.value = value
	def __iter__(self):
		return self
	def __next__(self):
		return self.value

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

    И вот где на сцену выходят генераторы Python. Если переписать этот класс итератора в качестве генератора, то он будет выглядеть так:

>>> def repeater(value):
	while True:
		yield value

    Мы только что перешли от семи строк кода к трем. Неплохо, правда? Как видите, генераторы похожи на обычные функции, но вместо инструкции возврата return в них для передачи данных назад источнику вызова используется инструкция yield.

    Будет ли эта новая реализация генератора по-прежнему работать так же, как и наш итератор на основе класса? Давайте стряхнем пыль с теста в цикле for ... in, чтобы это выяснить:

>>> for x in repeater('Привет'):
	print(x)

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

    Да! Мы по-прежнему без конца прокручиваем в цикле наши приветствия. Эта намного более короткая реализация генератора, по всей видимости, выполняется таким же образом, что и класс Repeater. (Не забудьте нажать Ctrl+C, если хотите выйти из бесконечного цикла в сеансе интерпретатора.)

    Итак, каким же образом эти генераторы работают? Они похожи на нормальные функции, но их поведение очень различается. Начнем с того, что вызов функции-генератора вообще не выполняет функцию. Он просто создает и возвращает объект-генератор:

>>> repeater('Эй')
<generator object repeater at 0x0000023AC23F7270>

    Программный код в функции-генератора исполняется только тогда, когда функция next() вызывается с объектом-генератором в качестве аргумента:

>>> generator_obj = repeater('Эй')
>>> next(generator_obj)
'Эй'

    Если вы еще раз прочитаете код функции repeater(), то увидите, что, судя по всему, ключевое слово yield каким-то образом останавливает эту функцию-генератор посередине исполнения, а затем возобновляет ее на более позднем этапе:

>>> def repeater(value):
	while True:
		yield value

    И это вполне подходящая ментальная модель того, что здесь происходит. Дело в том, что, когда инструкция return вызывается внутри функции, она безвозвратно передает управление назад источнику вызова функции. Когда же вызывается инструкция yield, она тоже передает управление назад источнику вызова функции - но она это делает лишь временно.

    В отличие от инструкции return, которая избавляется от локального состояния функции, инструкция yield приостанавливает функцию и сохраняет ее локальное состояние. На практике это означает, что локальные переменные и состояние исполнения функции-генератора лишь откладываются в сторону и не выбрасываются полностью. Исполнение может быть возобновлено в любое время вызовом функции next() с генератором в качестве аргумента:

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

    Это делает генераторы полностью совместимыми с протоколом итератора. По этой причине можно считать их, прежде всего, как синтаксический сахар для реализации итераторов.

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

    На следующем шаге мы рассмотрим генераторы, которые прекращают генерацию.




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