Асинхронное программирование становится всё важнее для современного Python-разработчика. Использование asyncio и ключевых слов async/await позволяет писать производительные приложения (от веб-серверов до парсеров и сервисов), не усложняя архитектуру потоками или процессами. В этой статье разберём практические паттерны асинхронного кода, частые ошибки и способы их избежать — с примерами и объяснениями.
Зачем нужна асинхронность в Python?
Обычное (синхронное) выполнение блокирует поток на каждом вводе-выводе: например, при ожидании ответа от сервера, файла или базы данных. В асинхронном подходе такие «ожидания» можно использовать разумнее — переключаясь на выполнение других задач, пока операция не завершилась.
-
Примеры применимости:
- Одновременные HTTP-запросы к API
- Параллельная обработка большого числа файлов
- Веб-серверы (например, на базе FastAPI, Starlette, aiohttp)
Какую выгоду это даёт?
- Более высокая производительность на I/O-зависимых задачах.
- Упрощение конструкции (нет необходимости в потоках, блокировках, синхронизации).
- Лучшее использование одного процесса Python — несмотря на GIL.
Базовые паттерны и примеры асинхронного кода
1. Асинхронные функции и await
Асинхронные функции объявляются с помощью async def и используются с await:
import asyncio
async def fetch_data():
print('Старт')
await asyncio.sleep(1)
print('Готово!')
asyncio.run(fetch_data())
Ключевые моменты:
asyncio.sleep()— типичный асинхронный эквивалент time.sleep().- При
awaitуправление возвращается event loop'у, другие задачи продолжают работу.
2. Одновременное выполнение задач
Вместо последовательного выполнения можно запускать множество задач одновременно:
import asyncio
async def download(url):
print(f'Скачиваю {url}')
await asyncio.sleep(1)
print(f'Готово: {url}')
async def main():
urls = ['a.com', 'b.com', 'c.com']
tasks = [asyncio.create_task(download(u)) for u in urls]
await asyncio.gather(*tasks)
asyncio.run(main())
Всё происходит в одном потоке, но задачи сами сдают управление во время ожидания.
3. Использование асинхронных библиотек
- aiohttp — для HTTP-клиентов и серверов
- aiosqlite — асинхронный доступ к SQLite
- asyncpg — высокопроизводительный PostgreSQL-клиент
Пример с aiohttp:
import aiohttp
import asyncio
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main():
html = await fetch('https://python.org')
print(len(html))
asyncio.run(main())
Частые ошибки и как их избежать
1. Смешивание sync и async: блокировки цикла
Не используйте в асинхронных функциях синхронных долгих операций (например, time.sleep() или CPU-bound вычислений). Это блокирует event loop, мешая масштабируемости.
await asyncio.sleep(1)вместоtime.sleep(1)- Для тяжёлых вычислений — вынесите их в ThreadPool или ProcessPool:
import asyncio
def sync_work():
... # CPU-bound функция
async def main():
loop = asyncio.get_running_loop()
result = await loop.run_in_executor(None, sync_work)
2. Неправильное создание и использование задач
- Не забывайте запускать асинхронные функции через
asyncio.run()(или внутри ивент-лупа). - Не используйте низкоуровневые API (
asyncio.get_event_loop()) без необходимости — предпочтительноasyncio.run()(Python 3.7+).
3. Исключения теряются в фоне
Если создать задачу через asyncio.create_task() и не дождаться её — исключения в ней могут остаться незамеченными. Всегда ждите await для создания/обработки ошибок:
task = asyncio.create_task(foo())
try:
await task
except Exception as ex:
print('Ошибка:', ex)
4. Неочевидное поведение gather/cancel
- asyncio.gather() не отменяет остальные задачи при исключении по умолчанию.
- Используйте
return_exceptions=Trueдля обработки всех ошибок:
results = await asyncio.gather(*tasks, return_exceptions=True)
Рекомендации и лучшие практики
Архитектура асинхронных проектов
- Строго отделяйте синхронный и асинхронный код.
- Используйте асинхронные библиотеки «по цепочке» (например, aiohttp + aioredis).
- Обрабатывайте исключения и отмену задач (cancellation).
- Проводите нагрузочное тестирование: реальную производительность видно только при параллельных запросах.
Инструменты для отладки и тестирования
- Базовый дебаг — через
print()иlogging(ложите логи до и после await) - Модули
pytest-asyncioиpytestдля тестирования async-функций async-timeoutдля явного контроля таймаутов асинхронных операций
Заключение
Асинхронное программирование в Python открывает возможности для масштабируемых сервисов и эффективной обработки задач, связанных с вводом-выводом. Осваивайте его шаг за шагом:
- Начните с
async/awaitи старта простых задач. - Постепенно интегрируйте асинхронные библиотеки в проекты.
- Следите за типичными ошибками и тестируйте свой код на исправность.
Совет: используйте асинхронность там, где действительно ждёте внешних ресурсов, а не везде подряд.
Помните — грамотная работа с асинхронным кодом ведёт к чистому, предсказуемому и быстрому Python-приложению.