Как реализовывались корутины в Python до появления синтаксиса async/await?

Ответ

До появления ключевых слов async/await в Python 3.5, асинхронность на базе asyncio реализовывалась с помощью генераторов и специального декоратора.

Основными инструментами были:

  1. Декоратор @asyncio.coroutine: Он помечал функцию-генератор как корутину, совместимую с циклом событий asyncio.
  2. Выражение yield from: Использовалось вместо современного await для передачи управления другой корутине и ожидания её результата.

По сути, async def является синтаксическим сахаром над генераторами, а await — над yield from.

Сравнение синтаксиса:

Старый стиль (Python 3.4):

import asyncio

@asyncio.coroutine
def old_style_fetch_data(url):
    print(f"Fetching {url}...")
    # Вместо await используется yield from
    response = yield from asyncio.sleep(1) # Имитация сетевого запроса
    print(f"Done fetching {url}.")
    return {"data": f"Some data from {url}"}

loop = asyncio.get_event_loop()
loop.run_until_complete(old_style_fetch_data("https://example.com"))

Современный стиль (Python 3.5+):

import asyncio

async def new_style_fetch_data(url):
    print(f"Fetching {url}...")
    # Используется async/await
    await asyncio.sleep(1)
    print(f"Done fetching {url}.")
    return {"data": f"Some data from {url}"}

asyncio.run(new_style_fetch_data("https://example.com"))

Основной недостаток старого подхода заключался в том, что синтаксически корутины не отличались от обычных генераторов, что могло приводить к путанице. Введение async/await сделало асинхронный код более явным и читаемым.