Достаточно часто можно услышать, что корутины произошли от генераторов. Живы еще те динозавры, которые помнят
@asyncio.coroutine в py3.4 и корутины на yieldОпределения
На самом деле, определить, что такое генератор и корутина - не так просто, как может показаться. Я пользуюсь следующими определениям:
- генератор = что-то, что соответствует интерфейсу _collections_abc.Generator
- корутина = что-то, что соответствует интерфейсу _collections_abc.Coroutine
(да, вы можете создавать свои нестандартные генераторы и корутины на Python и C)
Уже в самих интерфейсах, вы можете увидеть, что часть методов идентична, часть отличается:
>>> set(dir(gen)) - set(dir(coro))
{'gi_running', '__next__', 'gi_frame', 'gi_yieldfrom', 'gi_suspended', '__iter__', 'gi_code'}
>>> set(dir(coro)) - set(dir(gen))
{'cr_running', '__await__', 'cr_frame', 'cr_suspended', 'cr_origin', 'cr_code', 'cr_await'}
Копнем чуть глубже, создадим "generator function" и "coroutine function" (функции, которые вернут генератор / корутину):
import asyncio
async def coro_func():
await asyncio.sleep(1)
def gen_func():
yield 1
Байткод их создания будет абсотютно идентичным (разница только в именах):
2 LOAD_CONST 0 (<code object ...>)
MAKE_FUNCTION
STORE_NAME 0 (<NAME>)
Потому что сами объекты функций будут очень похожими, отличаться будут объекты
CodeObject, которые создаются компилятором в assemble.c. Проходимся по всем AST нодам в функции и вычисляем, есть ли там yield или yield from:
static int
symtable_visit_expr(struct symtable *st, expr_ty e)
{
switch (e->kind) {
// ...
case Yield_kind:
if (e->v.Yield.value)
VISIT(st, expr, e->v.Yield.value);
st->st_cur->ste_generator = 1; // теперь функция будет "generator function"
if (st->st_cur->ste_comprehension) {
return symtable_raise_if_comprehension_block(st, e);
}
break;
case YieldFrom_kind:
VISIT(st, expr, e->v.YieldFrom.value);
st->st_cur->ste_generator = 1;
if (st->st_cur->ste_comprehension) {
return symtable_raise_if_comprehension_block(st, e);
}
break;
}
}
И для корутины:
// symtable_visit_stmt(struct symtable *st, stmt_ty s)
case AsyncFunctionDef_kind: {
if (!symtable_add_def(st, s->v.AsyncFunctionDef.name, DEF_LOCAL, LOCATION(s)))
return 0;
// ...
st->st_cur->ste_coroutine = 1;
// symtable_visit_expr(struct symtable *st, expr_ty e)
case Await_kind:
if (!allows_top_level_await(st)) {
if (!_PyST_IsFunctionLike(st->st_cur)) {
return PyErr_SetString(PyExc_SyntaxError, "'await' outside function");
}
if (!IS_ASYNC_DEF(st) && st->st_cur->ste_comprehension == NoComprehension) {
return PyErr_SetString(PyExc_SyntaxError, "'await' outside async function");
}
}
VISIT(st, expr, e->v.Await.value);
st->st_cur->ste_coroutine = 1;
И потом уже просто вычисляем флаги для
CodeObject:
static int compute_code_flags(compiler *c)
{
if (_PyST_IsFunctionLike(ste)) {
flags |= ...;
if (ste->ste_generator && !ste->ste_coroutine)
flags |= CO_GENERATOR;
if (ste->ste_generator && ste->ste_coroutine)
flags |= CO_ASYNC_GENERATOR;
// ...
}
if (ste->ste_coroutine && !ste->ste_generator) {
flags |= CO_COROUTINE;
}
return flags;
}
Результат:
>>> from inspect import CO_COROUTINE, CO_GENERATOR
>>> gen_func.__code__.co_flags & CO_GENERATOR
32
>>> coro_func.__code__.co_flags & CO_GENERATOR
0
>>> coro_func.__code__.co_flags & CO_COROUTINE
128
Что происходит при вызове?
Если сделать
dis функций gen_func и coro_func, то у них первым байткодом будет RETURN_GENERATOR (см _Py_MakeCoro):