Асинхронные функции: async def, await и работа с корутинами
🔄 Синхронный vs Асинхронный Python: зачем вообще это нужно?
Представьте, что ваш код — это повар в ресторане. Синхронный повар готовит один заказ за раз: варит суп, ждёт пока он сварится, потом жарит стейк, опять ждёт... Асинхронный же повар начинает варить суп, пока тот кипит — переключается на стейк, и так управляет десятком блюд одновременно!
Проблема синхронного кода:
import time
def make_coffee():
time.sleep(3) # Ждём 3 секунды
return "Кофе готов!"
def make_toast():
time.sleep(2) # Ждём 2 секунды
return "Тосты готовы!"
# Общее время выполнения: 5 секунд!
breakfast = [make_coffee(), make_toast()]
🐍 Корутины: сердце асинхронности
Корутина (coroutine) — это функция, которая может приостанавливать своё выполнение и передавать управление другим корутинам. В Python они создаются через async def.
Базовый пример:
import asyncio
async def make_coffee():
print("Начинаем варить кофе...")
await asyncio.sleep(3) # Не блокируем поток!
return "Кофе готов!"
# Запуск корутины
coffee = make_coffee()
print(coffee) # <coroutine object make_coffee at 0x...>
Важные моменты:
- Корутина не выполняется сразу при вызове, а возвращает coroutine-объект
- Для запуска нужен событийный цикл (event loop)
await— ключевое слово для ожидания результатов других корутин
⚡ await: волшебный ключ к асинхронности
Оператор await делает две важные вещи:
- Приостанавливает выполнение текущей корутины
- Передаёт управление обратно в событийный цикл
Правила использования:
awaitможно использовать только внутриasync def- Объект после
awaitдолжен быть "awaitable" (корутина, Task, Future и т.д.)
async def breakfast():
coffee_task = asyncio.create_task(make_coffee())
toast_task = asyncio.create_task(make_toast())
# Параллельное выполнение!
coffee = await coffee_task
toast = await toast_task
print(f"{coffee} и {toast} - завтрак подан!")
🛠 Практика: делаем асинхронный HTTP-запрос
Давайте попробуем реальный пример с библиотекой aiohttp:
import aiohttp
import asyncio
async def fetch_data(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
urls = [
'https://python.org',
'https://github.com',
'https://habr.com'
]
tasks = [fetch_data(url) for url in urls]
results = await asyncio.gather(*tasks)
for url, content in zip(urls, results):
print(f"{url}: {len(content)} символов")
# Запускаем событийный цикл
asyncio.run(main())
Что происходит:
- Создаются задачи для каждого URL
asyncio.gatherзапускает их параллельно- Мы получаем все результаты, когда они готовы
💡 Важные нюансы работы с корутинами
- Не смешивайте sync и async код — это может заблокировать event loop
- Используйте специальные async-библиотеки (aiohttp, asyncpg, aioredis)
- Обрабатывайте ошибки через try/except в корутинах
- Ограничивайте параллелизм с помощью семафоров:
semaphore = asyncio.Semaphore(5) # Максимум 5 одновременных запросов
async def limited_fetch(url):
async with semaphore:
return await fetch_data(url)
🚀 Главные преимущества асинхронности
- Экономия времени — параллельное выполнение I/O операций
- Масштабируемость — тысячи одновременных соединений
- Реактивность — приложения не "зависают" на долгих операциях