Что такое корутина (coroutine) в Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое корутина (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 — это бесконечный цикл, который:
- Запускает корутину
- Когда встречает
await, приостанавливает её - Запускает другую корутину
- Когда данные готовы, возобновляет приостановленную корутину
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 — мощный инструмент для асинхронного программирования. Ключевые моменты:
- Определение:
async def+await - Event Loop: управляет выполнением множества корутин
- Параллелизм: кажущийся параллелизм с одним потоком (cooperative multitasking)
- Ideal для: I/O-bound операции (сетевые запросы, работа с файлами)
- Не подходит: CPU-bound операции (используй multiprocessing)
Практическое применение:
- Web scraping — загрузка много URL
- API servers — обработка тысяч одновременных запросов
- Real-time приложения — WebSockets, live notifications
- Background tasks — обработка очередей
Для интервью важно понимать разницу между синхронным и асинхронным кодом, когда использовать корутины, и как написать простой асинхронный код с asyncio.