async def)Асинхронные функции (async def) не запускаются сразу: вызов возвращает объект-сопрограмму. Код внутри стартует только при await или когда корутину оборачивают в Task и планируют в event loop. Внутри async def можно использовать await, async for, async with, а результат и исключения возвращаются через await так же, как в обычных функциях через return и raise.
async def func(): return 42
Такая запись не делает функцию асинхронной "по поведению" сразу.
Важно:
async def определяет функцию-корутину (coroutine function);func() не запускает код сразу, а создаёт объект-сопрограмму (coroutine object).coro = func() # код внутри ещё не выполнялся
Код внутри начнёт выполняться только при:
await coro, илиasyncio.create_task).Обычная функция:
Асинхронная функция:
await).Пример:
def normal(): return 1 async def async_func(): return 2 x = normal() # x == 1 y = async_func() # y - coroutine object, не число
Чтобы получить 2:
result = await async_func()
или в более полном виде:
import asyncio async def main(): result = await async_func() print(result) asyncio.run(main())
async defВнутри асинхронной функции можно использовать:
await - ожидание другого awaitable;async for - асинхронная итерация;async with - асинхронные контекстные менеджеры;await ожидает awaitable - объект, который реализует протокол __await__ или является другой корутиной/задачей.
async def handler(): data = await fetch_data() async for item in stream_items(): await process_item(item)
Асинхронная функция логически похожа на обычную по семантике return / исключений:
return value внутри async def => при await получаем value;async def всплывает при await.async def f(): return 10 async def g(): raise ValueError("error") async def main(): print(await f()) # 10 print(await g()) # возбуждается ValueError asyncio.run(main())
Сопрограмму (объект, полученный из async def) обычно не вызывают напрямую из синхронного кода. Нужен цикл событий (asyncio):
asyncio.run(main());main создаются и ожидаются другие корутины.Для параллельного выполнения нескольких сопрограмм используется Task:
async def worker(name): await asyncio.sleep(1) print(f"done {name}") async def main(): t1 = asyncio.create_task(worker("A")) t2 = asyncio.create_task(worker("B")) await t1 await t2 asyncio.run(main())
create_task:
На уровне CPython (упрощённо):
async def компилируется в coroutine function;coroutine, который:code),frame) с локальными переменными,__await__.При await coro:
coro.__await__(), получая итератор.yield from).StopIteration с value => завершение сопрограммы с этим результатом.await.Исторически await во многом эквивалентен yield from для специальных "async" объектов, но с более строгой типизацией и отдельным байткодом.
async def, когда внутри:await;await;asyncio.create_task + последующий await задачи;asyncio.run(main()) и держим всю логику в async def main().Сопрограмма - это "приостанавливаемая функция": она выполняется до ближайшего await, отдаёт управление циклу событий и позже продолжается с того же места. Это даёт кооперативную многозадачность: переключения происходят только там, где явно есть await. В отличие от потоков, корутины лёгкие, управляются на уровне Python, хорошо подходят для I/O-bound задач, поддерживают отмену (CancelledError) и требуют аккуратного cleanup через try/finally и/или async-контекстные менеджеры.
Сопрограмма - это единица вычисления, которая:
В Python:
def + yield) - тоже вид сопрограмм;async def) - native coroutines (современный механизм).Главная идея:
Сопрограммы дают кооперативную многозадачность: они явно говорят, когда готовы "уступить" управление.
В современных версиях Python основным механизмом являются:
async def.Есть также устаревший механизм:
@asyncio.coroutine + yield from (используется всё реже, оставлен в основном для обратной совместимости).Мы фокусируемся на native coroutines.
__await__Сопрограмма - частный случай awaitable. Awaitable - это объект, который можно передать в await:
async def);asyncio.Task;__await__, возвращающий итератор.Когда пишем:
result = await some_awaitable
внутри происходит:
some_awaitable.__await__() => получаем итератор.StopIteration со значением),Корутины async def предоставляют корректную реализацию __await__ автоматически.
У любой корутины есть типичный жизненный цикл:
async def создаёт coroutine object, но код ещё не выполнялся.await coro в другой корутине;asyncio.create_task(coro).await, где управление отдаётся event loop;await.return => результат доставляется через await;await.В отличие от потока:
await, а не принудительно планировщиком ОС.Ключевые отличия:
await;await, код выполняется "атомарно" относительно других корутин.Практически:
Часто строится "пирамидальная" структура:
async def low_level(): ... async def mid_level(): result = await low_level() ... async def high_level(): await mid_level()
Каждый await:
low_level всплывёт через mid_level в high_level.Можно рассматривать это как асинхронный call stack.
Сопрограммы могут быть отменены:
task.cancel() для asyncio.Task.При отмене:
asyncio.CancelledError в ближайшей точке ожидания (await).await task снаружи тоже получит CancelledError.Поэтому внутри длительных корутин:
try/finally для корректного cleanup (освобождение ресурсов, закрытие соединений и т.п.);finally может содержать await.Упрощённо:
Event loop:
Task) набор корутин;await на операции I/O;То есть корутина - это "приостанавливаемая функция", а event loop - менеджер, который решает, какую корутину и когда возобновить.