В предыдущей статье Итерируемые объекты, итераторы и генераторы в Python я уже затрагивал тему генераторов. В этой статье разберемся с тем, как работает оператор yield, и в чем разница между генераторами и корутинами. Будет проще понять эту статью, если прочитаете предыдущую.
Генераторы
Генератор – функция, которая генерирует последовательность значений, вместо одного значения, как это делает обычная функция. Любая функция, в которой есть оператор yieldявляется генераторной:
>>> def fibonacci(): ... a, b = 0, 1 ... while True: ... yield a ... a, b = b, a + b >>> # Работаем с функцией вручную >>> f = fibonacci() <generator object fibonacci at 0x7f1d96f56990> >>> next(f) 0 >>> next(f) 1 >>> ... >>> # Через цикл for >>> for num in fibonacci(): ... if num > 42: ... break # иначе цикл будет бесконечный ... print(num) 0 1 1 2 3 ...
Функция порождает (производит) числа Фибоначчи по одному через оператор yield. После каждого yield, генераторная функция приостанавливается и выполнение программы переходит к вызывающей стороне. Генераторная функция продолжает работу после вызова функции next(…).
В примере с числами Фибоначчи, генераторная функция работает бесконечно. Она ничего не возвращает (return). Если генераторная функция завершает работу и в конце возвращает какое-то значение, то после этого выбрасывается исключение StopIteration. Это исключение можно словить и получить значение, которое вернул генератор:
>>> def gen(): ... yield 'Yield something' ... return 'Return something' >>> g = gen() >>> next(g) 'Yield something' >>> try: ... next(g) ... except StopIteration as exc: ... print(exc.value) Return something
Напомню, что любая функция возвращает какое-то значение. Если оператор return не указан явно, то функция возвращает None. Поэтому, после завершения работы генераторной функции, исключение StopIteration выбрасывается в любом случае.
Корутины
Генераторная функция порождает значения для вызывающей стороны через оператор yield. Однако, генераторная функция также может и получать значения от вызывающей стороны. Генераторы, которые получают данные от вызывающей стороны называются корутинами (сопрограммами на русском). Все корутины являются генераторами. Но не наоборот, не путайте.
Передача данных в генератор осуществляется через тот же оператор yield. Только при получении данных, оператор yield находится в правой части выражения:
>>> def double():
... print('> Начало функции')
... value = 2 * (yield)
... print('> value = {}'.format(value))
... yield value
... print('> Конец функции')
>>> d = double()
>>> next(d)
> Начало функции
>>> d.send(21) # метод send объекта генератора передает данные в функцию
> value = 42
42
>>> d.send(42)
> Конец функции
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Последовательность работы функции double показана на картинке:

Как вы уже поняли, yield – двухсторонний оператор. Сначала генераторная функция передает значение вызывающей стороне (yield something). Затем останавливается и ждет, пока вызывающая сторона не передаст ей что-нибудь в ответ (generator.send(something)), чтобы она могла сохранить это значение (something = yield) и продолжить работу до следующего оператора yield.
yield является двухсторонним оператором всегда, даже если вы не передаете значение генератору через send, а просто пытаетесь продолжить работу генератора через next:
>>> def hello():
... value = yield 'Hello'
... print('value = {}'.format(value))
>>> gen = hello()
>>> next(gen)
'Hello'
>>> next(gen)
value = None # так как мы ничего не передали в генератор
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
Аналогично и в ситуации, когда yield используется только для получения данных. В таком случае, yield передает None вызывающей стороне:
>>> def simple_coroutine(): ... value = yield ... print(value) >>> coro = simple_coroutine() >>> from_coro = next(coro) >>> print(from_coro) None >>> coro.send(42) 42 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Заключение
В статье разобрались с оператором yield, и в чем разница между генераторами и корутинами.
Хоть генераторы и чем-то схожи с корутинами, но корутины довольно объемная тема, в которой много чего еще интересного. От оператора yield from и до пакета asyncio, который, по сути, работает на корутинах. В статье я затронул лишь самые основы.
Отличное объяснение.
А статья про yield from есть ?
И еще пофикси фокус обязательных инпутов в комментариях – невозможно комментировать… Или это отбор комментирующих ?
Привет. Статьи про yield from пока нет
Было бы здорово почитать статью по asyncio. Хорошо и доступно пишите!