В предыдущей статье Итерируемые объекты, итераторы и генераторы в 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. Хорошо и доступно пишите!