Корутины похожи на генераторы за исключением нескольких отличий, основные из которых:
- генераторы возвращают данные
- корутины потребляют данные
Для начала рассмотрим процесс создания генератора. Мы можем создать один таким образом:
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
Такой генератор зачастую используется в цикле for
:
for i in fib():
print(i)
Такой подход отличается скоростью и отсутствием повышенной нагрузки на память,
поскольку значения генерируются "на лету" и не хранятся в списке. Теперь,
если мы используем yield
в примере выше, то, условно говоря, получим
корутину. Корутины потребляют данные, которые им передаются. Вот простой пример
реализации grep
на Python:
def grep(pattern):
print("Searching for", pattern)
while True:
line = (yield)
if pattern in line:
print(line)
Подождите! Что возвращает yield
? Ну, мы преобразовали её в корутину.
Сначала она не содержит значения, вместо этого мы передаём значение из внешнего
источника. Мы передаем значения используя метод .send()
. Вот простой
пример:
search = grep('coroutine')
next(search)
# Вывод: Searching for coroutine
search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutines instead!")
# Вывод: I love coroutines instead!
Передаваемые значения используются в yield
. Почему мы вызвали next()
?
Это требуется для запуска корутины. Так же как и в случае с генераторами,
корутины не запускают функцию сразу же. Вместо этого они запускают её в ответ
на вызов методов __next__()
и .send()
. По этой причине вам требуется
вызвать next()
, чтобы исполнение дошло до yield
.
Мы можем закрыть корутину при помощи метода .close()
:
search = grep('coroutine')
# ...
search.close()
Это лишь основы работы с корутинами. Рекомендую дополнительно просмотреть эту шикарную презентацию от David Beazley.