Что такое корутина?
Корутина — это объект, представляющий приостанавливаемое вычисление. async def возвращает coroutine object, который не выполняется сам по себе — его нужно await-ить, обернуть в Task или запустить через asyncio.run, иначе появится warning "coroutine was never awaited".
Определение
Корутина в Python — обобщённая функция, выполнение которой можно приостанавливать в точках await и возобновлять позже. Технически это объект типа types.CoroutineType, у него есть методы send(), throw(), close() — как у генератора, но он не реализует __iter__ и не годится для for.
Создание и запуск
import asyncio
async def compute(x): # это coroutine function
await asyncio.sleep(0.1)
return x * 2
coro = compute(21) # это coroutine object, тело ещё НЕ выполнялось
async def main():
result = await coro # вот здесь выполняется
print(result) # 42
asyncio.run(main())
Если сделать только compute(21) и ничего с этим объектом не сделать, Python выдаст RuntimeWarning: coroutine 'compute' was never awaited.
Coroutine vs Task vs Future
- Coroutine — описание работы, само по себе не исполняется.
- Task — subclass
Future, обёртка вокруг coroutine, которая планирует её на event loop черезasyncio.create_task(coro). Создание Task сразу же ставит coroutine на исполнение. - Future — placeholder результата без привязки к корутине, его выставляет, например, low-level транспорт.
Native vs generator-based
До Python 3.5 корутины писали через @asyncio.coroutine и yield from. С 3.5 появился async def / await, а в 3.10 legacy-форма официально удалена. Современный код пишут только через native syntax.
Запуск нескольких
async def main():
# последовательно — 0.3 сек
a = await compute(1)
b = await compute(2)
# параллельно — 0.1 сек
a, b = await asyncio.gather(compute(1), compute(2))
# с TaskGroup (3.11+) — корректная отмена при ошибке
async with asyncio.TaskGroup() as tg:
t1 = tg.create_task(compute(1))
t2 = tg.create_task(compute(2))
Подводные камни
- Забыли await — coroutine не исполнилась, warning, баг тихий.
- Считать, что
awaitзапускает несколько корутин одновременно — нет, это последовательное ожидание. - Запуск
asyncio.run()внутри уже запущенного loop —RuntimeError: asyncio.run() cannot be called from a running event loop. - Coroutine не является thread-safe в смысле shared state — синхронизируйтесь через
asyncio.Lock. - Smell: блокирующие вызовы (
time.sleep,requests) внутри coroutine блокируют весь loop.
Common mistakes
- Говорить, что async def сразу выполняется.
- Не различать coroutine object и результат.
- Не знать warning never awaited.
What the interviewer is testing
- Объясняет вызов async def.
- Понимает await.
- Различает coroutine и Task на базовом уровне.