суббота, 23 февраля 2019 г.

Сопрограммы в языке Python

В программировании существует понятия подпрограмма и сопрограмма. Не говоря об асинхронном программировании, можно сказать, что подпрограмма это код, вызываемый из разных мест программы. Этот код возвращает результат в то место, из которого его вызвали. Программа его ждет. Сопрограмма также вызывается из программы, выполняет какие то действия, а потом возвращает промежуточный результат и ждет следующего вызова. При следующем вызове она продолжает с того места, где прервалась на прошлом вызове. Вызывающая программа также ждет сопрограмму.

 В языке Python на идеях сопрограмм реализованы конструкции генераторов-итераторов. Эти функции при первом обращении организуют сопрограмму, а запускают обработку командой .next(). Возврат из сопрограммы промежуточных результатов выполняется оператором yield. Когда итерация закончена, сопрограмма выполняет оператор return. Построенная подобным способом сопрограмма может выполняться в циклах языка Python.

Подробнее этот алгоритм описан в PEP 255. Далее я привожу перевод части этого документа.


Определение: Yield

Вводится новое предложение:
yield_stmt:    "yield" expression_list
Предложение yield можно использовать только внутри функций. Функция, содержащая предложение yield называется функцией генератором. Функция генератор во всех отношениях является обычной функцией, но в члене кодового объекта co_flags этой функции, установлен новый флаг CO_GENERATOR.
Когда функция генератор вызывается, актуальные аргументы как обычно связываются с именами формальных аргументов локальной функции, но код в теле этой функции не выполняется. Вместо этого возвращается объект типа генератор-итератор; это соответствует протоколу итератора, поэтому, в частности, может использоваться в циклах for естественным образом. Обратите внимание, что когда намерение ясно из контекста, безоговорочное имя «генератор» может использоваться для ссылки либо на функцию-генератор, либо на генератор-итератор.

Каждый раз, когда вызывается метод .next() генератора-итератора, начинает выполняться код в теле функции-генератора до тех пор пока не встретятся предложения yield или return, или пока не будет достигнут конеч тела процедуры.

Если встречается предложение yield, статус функции замораживается и значение expression_list возвращается вызвавшему .next(). Под "замораживанием" мы подразумеваем что сохраняется все локальное состояние, включая указатели на локальные переменные, указатели на инструкции, и внутренний стэк: сохраняется достаточно информации для того чтобы при следующем вызове .next(), функция могла бы действовать точно так же, как если бы оператор yield был просто еще одним внешним вызовом..
Ограничение: Предложение yield нельзя использовать в выражении try из конструкции try/finally. Сложность состоит в том, что нет никакой гарантии, что генератор когда-либо будет возобновлен, следовательно, нет никакой гарантии, что блок finally когда-либо будет выполнен.
Ограничение: генератор не может быть возобновлен во время его активной работы:
>>> def g():
...     i = me.next()
...     yield i
>>> me = g()
>>> me.next()
Traceback (most recent call last):
 ...
 File "", line 2, in g
ValueError: generator already executing

Определение: Return

Функция-генератор может содержать предложения возврата в виде:
return
Обратите внимание на то, что список-выражений(expression_list) нельзя указывать в предложениях return в теле генераторов (хотя, конечно, их можно указывать в теле функций не-генераторов, которые находятся внутри функций-генераторов).
Когда встречается предложение return, управление происходит как и в любой другой функции. Сначала выполняются соответствующие завершающие выражения (finally clauses) (если они есть). Затем инициируется исклбчение StopIteration, сигнализируя что итератор закончен. Исключение StopIteration инициализируется и в случае если генератор закончил свою работу без явного предложения return.
Обратите внимание, что return не всегда эквивалентен инициализации StopIteration: разница заключается в том, как обрабатываются включающие конструкции try/except. Например,:
>>> def f1():
...     try:
...         return
...     except:
...        yield 1
>>> print list(f1())
[]
потому, что здесь как в любой функции return это просто возврат, но:
>>> def f2():
...     try:
...         raise StopIteration
...     except:
...         yield 42
>>> print list(f2())
[42]
А здесь исключение StopIteration как любое исключение заставляет выполниться выражение в правиле except.

X-Plane 11, 12 - любитель, Фото любитель со стажем

Постоянные читатели

Архив блога