← Назад к вопросам

Что такое корутина (coroutine) в Python?

2.3 Middle🔥 171 комментариев
#DevOps и инфраструктура#Django

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое корутина (coroutine) в Python?

Определение

Корутина (coroutine) — это функция, которая может быть приостановлена и возобновлена, сохраняя своё состояние. В Python корутины определяются с ключевым словом async и используют await для приостановки выполнения, позволяя другому коду выполняться. Это основа асинхронного программирования в Python.

Отличие корутин от обычных функций:

  • Обычная функция выполняется полностью за один раз
  • Корутина может быть приостановлена и возобновлена по мере необходимости

История и эволюция в Python

Python 3.3 — введены генераторные корутины (generators с yield)

def old_style_coroutine():
    result = yield  # Может получить значение
    yield result

Python 3.5 — асинхронные функции (async/await)

async def modern_coroutine():
    result = await some_async_function()
    return result

Python 3.7+ — стандартизация asyncio, улучшения производительности

Современный стандарт — async/await, что более понятно и безопасно.

Основные компоненты

1. Определение корутины (async def)

async def fetch_data(url):
    """
    Асинхронная функция — корутина.
    Может содержать await выражения.
    """
    response = await http_get(url)  # Приостанавливается здесь
    data = await response.json()
    return data

# Вызов корутины возвращает объект coroutine, НЕ выполняет его
coro = fetch_data("https://api.example.com")
print(type(coro))  # <class coroutine>

# Для выполнения нужен event loop
import asyncio
result = asyncio.run(coro)  # Запускает event loop и выполняет

2. Await выражение

await может использоваться только внутри корутины. Он:

  • Приостанавливает выполнение корутины
  • Позволяет event loop выполнять другие корутины
  • Когда результат готов, возобновляет выполнение
async def main():
    # await можно использовать на:
    # 1. Другие корутины
    result1 = await some_coroutine()
    
    # 2. Объекты с __await__ методом
    result2 = await asyncio.sleep(1)
    
    # 3. Future/Task объекты
    result3 = await asyncio.create_task(some_coroutine())
    
    return result1, result2, result3

asyncio.run(main())

Практические примеры

Пример 1: Загрузка нескольких URL асинхронно

import asyncio
import aiohttp

async def fetch_url(session, url):
    """
    Асинхронно загружает URL.
    """
    async with session.get(url) as response:
        return await response.text()

async def main():
    """
    Загружает несколько URL параллельно.
    """
    urls = [
        "https://api.github.com/users/github",
        "https://api.github.com/users/google",
        "https://api.github.com/users/facebook"
    ]
    
    async with aiohttp.ClientSession() as session:
        # Создаём задачи для каждого URL
        tasks = [fetch_url(session, url) for url in urls]
        
        # Выполняем все параллельно
        results = await asyncio.gather(*tasks)
        
        return results

# Запуск
results = asyncio.run(main())
print(f"Загружено {len(results)} страниц")

Пример 2: Простой timeout и обработка ошибок

async def long_running_operation():
    """
    Операция, которая может долго выполняться.
    """
    await asyncio.sleep(10)
    return "Готово!"

async def with_timeout():
    """
    Выполняет операцию с timeout.
    """
    try:
        result = await asyncio.wait_for(
            long_running_operation(),
            timeout=2.0  # Ждём максимум 2 секунды
        )
        return result
    except asyncio.TimeoutError:
        return "Операция истекла"

asyncio.run(with_timeout())  # Выведет: "Операция истекла"

Пример 3: Consumer-Producer паттерн с Queue

import asyncio

async def producer(queue, n):
    """
    Производитель: добавляет значения в очередь.
    """
    for i in range(n):
        item = f"item-{i}"
        await queue.put(item)  # Добавляет в очередь
        print(f"Произвёл: {item}")
        await asyncio.sleep(0.5)  # Имитирует работу

async def consumer(queue, id):
    """
    Потребитель: извлекает значения из очереди.
    """
    while True:
        item = await queue.get()  # Ждёт элемента в очереди
        if item is None:  # Сигнал к остановке
            break
        print(f"Потребитель {id} обработал: {item}")
        await asyncio.sleep(0.2)  # Имитирует обработку
        queue.task_done()

async def main():
    queue = asyncio.Queue(maxsize=5)
    
    # Запускаем производителя и нескольких потребителей
    await asyncio.gather(
        producer(queue, 5),
        consumer(queue, 1),
        consumer(queue, 2)
    )

asyncio.run(main())

Пример 4: Асинхронный контекстный менеджер

class AsyncResource:
    """
    Ресурс, требующий асинхронной инициализации.
    """
    async def __aenter__(self):
        print("Инициализация ресурса")
        await asyncio.sleep(0.5)  # Имитирует асинхронную инициализацию
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("Очистка ресурса")
        await asyncio.sleep(0.5)  # Имитирует асинхронную очистку
        return False
    
    async def do_work(self):
        await asyncio.sleep(1)
        return "Работа завершена"

async def main():
    async with AsyncResource() as resource:
        result = await resource.do_work()
        print(result)

asyncio.run(main())

Event Loop: сердце асинхронности

Event Loop — это бесконечный цикл, который:

  1. Запускает корутину
  2. Когда встречает await, приостанавливает её
  3. Запускает другую корутину
  4. Когда данные готовы, возобновляет приостановленную корутину
async def task1():
    print("Задача 1 начало")
    await asyncio.sleep(2)
    print("Задача 1 конец")

async def task2():
    print("Задача 2 начало")
    await asyncio.sleep(1)
    print("Задача 2 конец")

async def main():
    # Выполняем параллельно
    start = asyncio.get_event_loop().time()
    
    await asyncio.gather(task1(), task2())
    
    end = asyncio.get_event_loop().time()
    print(f"Всего времени: {end - start:.1f}s")  # ~2.0s, не 3.0s!

asyncio.run(main())

# Вывод:
# Задача 1 начало
# Задача 2 начало
# Задача 2 конец
# Задача 1 конец
# Всего времени: 2.0s

Корутины vs Потоки vs Процессы

КонцепцияПамятьПереключениеI/O операцииCPU-bound
Корутины (asyncio)Минимум (тысячи)Явное (yield/await)ОтличноПлохо
Потоки (threading)~8 MB каждыйОС (любой момент)ХорошоТак себе (GIL)
Процессы (multiprocessing)МногоОСХорошоОтлично
# Корутины для I/O-bound задач (сетевые запросы, файлы)
async def fetch_many():
    tasks = [fetch_url(url) for url in urls]
    return await asyncio.gather(*tasks)  # Параллельно

# Потоки для moderate I/O
import threading
threads = [threading.Thread(target=fetch_url, args=(url,)) for url in urls]
for t in threads:
    t.start()
for t in threads:
    t.join()

# Процессы для CPU-bound задач
from multiprocessing import Pool
with Pool(4) as pool:
    results = pool.map(cpu_intensive_function, data)

Важные функции asyncio

import asyncio

# asyncio.run() — запускает корутину
result = asyncio.run(some_coroutine())

# asyncio.create_task() — запускает корутину в фоне
task = asyncio.create_task(some_coroutine())
await task

# asyncio.gather() — выполняет несколько корутин параллельно
results = await asyncio.gather(coro1(), coro2(), coro3())

# asyncio.wait_for() — timeout для корутины
try:
    result = await asyncio.wait_for(long_operation(), timeout=5)
except asyncio.TimeoutError:
    print("Истекло")

# asyncio.sleep() — асинхронная задержка
await asyncio.sleep(1)  # Не блокирует event loop!

# asyncio.Queue() — асинхронная очередь
queue = asyncio.Queue()
await queue.put(item)
item = await queue.get()

# asyncio.Lock() — асинхронный лок
lock = asyncio.Lock()
async with lock:
    # Критическая секция
    pass

Общие ошибки

# ❌ ОШИБКА 1: Забыли await
async def get_data():
    data = fetch_data()  # Возвращает корутину, но не выполняет её!
    return data

# ✅ ПРАВИЛЬНО:
async def get_data():
    data = await fetch_data()  # Выполняет и получает результат
    return data

# ❌ ОШИБКА 2: Смешивание sync и async
async def async_func():
    time.sleep(1)  # БЛОКИРУЕТ event loop!
    return "Готово"

# ✅ ПРАВИЛЬНО:
async def async_func():
    await asyncio.sleep(1)  # Не блокирует
    return "Готово"

# ❌ ОШИБКА 3: Вызов async функции без await
result = fetch_data()  # Это просто объект coroutine!

# ✅ ПРАВИЛЬНО:
result = await fetch_data()  # Выполняет и получает результат

Заключение

Корутины в Python — мощный инструмент для асинхронного программирования. Ключевые моменты:

  1. Определение: async def + await
  2. Event Loop: управляет выполнением множества корутин
  3. Параллелизм: кажущийся параллелизм с одним потоком (cooperative multitasking)
  4. Ideal для: I/O-bound операции (сетевые запросы, работа с файлами)
  5. Не подходит: CPU-bound операции (используй multiprocessing)

Практическое применение:

  • Web scraping — загрузка много URL
  • API servers — обработка тысяч одновременных запросов
  • Real-time приложения — WebSockets, live notifications
  • Background tasks — обработка очередей

Для интервью важно понимать разницу между синхронным и асинхронным кодом, когда использовать корутины, и как написать простой асинхронный код с asyncio.