Пояснення Python async / await: Підручник 2026
Async у Python має репутацію заплутаного. Це не повинно бути. Після того, як ви засвоїли одне речення — async дозволяє ОДНОМУ потоку виконувати кілька завдань, пов’язаних із вводом-виводом, чергуючи — все інше слідує.
Цей підручник охоплює: що насправді роблять async def і await, коли async перемагає, а коли це неправильний інструмент, чотири шаблони, які використовує кожна база асинхронного коду (послідовний, збір, семафор, тайм-аут), і три способи, як новачки мовчки ковтають помилки.
Ментальна модель з одного речення
Async у Python — це кооперативний паралелізм в одному потоці. Коли ваш код натискає await, керування повертається до циклу подій, який може запускати інше завдання, поки перше очікує на введення-виведення. Після завершення вводу/виводу цикл відновлює перше завдання з місця, на якому він був призупинений.
Ось і все. Ні потоків, ні процесів, ні паралелізму (у сенсі CPython-with-GIL). Лише один потік жонглює багатьма завданнями, які витрачають більшу частину часу на очікування в мережі/на диску/базі даних.
Асинхронізація працює швидше, коли ваше вузьке місце чекає, а не обчислюється.
Тристрокова мінімальна життєздатна асинхронна програма
async def hello()— оголошує співпрограму. Викликhello()не запускає код; він створює об'єкт співпрограми.await asyncio.sleep(1)— повертає керування циклу подій на 1 секунду.asyncio.run(hello())— створює цикл подій і запускає співпрограму до кінця.
Правило: `await` тільки всередині `async def`. Виклик await у звичайній функції є SyntaxError.
Коли асинхронний перемагає (а коли ні)
Перемагає асинхронний режим: робота вводу-виводу
Будь-яке завдання, яке витрачає більшу частину свого часу на настінному годиннику В ОЧІКУВАННІ, є кандидатом на асинхронне:
- HTTP-запити (зовнішні API, скрапінг, веб-хуки)
- Запити до бази даних
- Файл читає / записує
- Потоки WebSocket / gRPC
- Споживачі черги (Redis, RabbitMQ, Kafka)
Приклад: отримання 100 URL-адрес.
50-100-кратне прискорення настінного годинника для інтенсивної роботи введення-виведення. Ось де асинхронний заробляє свій бюджет складності.
Async ПРОТРАЧАЄ: робота, пов’язана з ЦП
Async не дає паралелізму для обчислень. Оскільки це кооперативний однопотоковий цикл, гарячий цикл ЦП блокує весь цикл подій.
Під час роботи pbkdf2 усі інші завдання не працюють. Для роботи, пов’язаної з процесором, використовуйте multiprocessing або concurrent.futures.ProcessPoolExecutor, а не async.
Змішане навантаження: run_in_executor
Якщо ОДИН крок навантажує ЦП, а решта — введення/виведення, оберніть крок ЦП так, щоб він працював у пулі потоків без блокування циклу:
None = пул потоків за замовчуванням. Чудово підходить для виклику сторонніх бібліотек синхронізації, які працюють повільно, але ви не можете переписати.
Чотири шаблони
1. Послідовний — одне завдання за раз
Використовуйте, коли кожен запит залежить від результату попереднього.
2. asyncio.gather — запускає все одночасно
Вибір за замовчуванням для незалежних завдань введення-виведення. Але: якщо ви зберете 10 000 завдань для одного API, ви отримаєте дескриптори файлів з обмеженою швидкістю або вичерпані. Ось тут і з’являється шаблон семафора.
3. Семафор — максимальний паралелізм
asyncio.Semaphore(N) дозволяє щонайбільше N завдань після async with sem: в будь-який момент. Черга інших завдань. Використовуйте для будь-якого зовнішнього API з обмеженням швидкості або бази даних із пулом підключень.
4. Таймаут — відмова від повільної роботи
asyncio.wait_for скасовує основне завдання через N секунд. Важливо для будь-якого асинхронного коду, який спілкується з мережею — ніколи не робіть необмеженого очікування.
Три пастки, що ковтають помилки
Пастка 1: asyncio.gather з return_exceptions=True
Корисно, коли ви хочете зберегти часткові результати, але СМЕРТЕЛЬНО, якщо ви не перевіряєте кожен результат:
Без перевірки помилки мовчки зникають.
Пастка 2: завдання типу «запали й забудь».
Якщо background_work() піднімається, Python видає попередження під час завершення роботи та продовжує роботу. Для всього, що вас цікавить, збережіть завдання та await його або скористайтеся asyncio.TaskGroup (3.11+), який поширює всі помилки.
Пастка 3: код синхронізації всередині async
requests є синхронним. Навіть усередині async def requests.get блокує весь цикл, доки не прийде відповідь. Використовуйте httpx (або aiohttp) для асинхронного HTTP.
Правило: ніколи не використовуйте бібліотеку синхронного введення-виведення в асинхронному коді. Якщо бібліотека не має асинхронного варіанту, загорніть її в run_in_executor.
asyncio.TaskGroup — 2026 спосіб розгортання
У Python 3.11 додано TaskGroup, який чітко вирішує проблему "запустив і забув":
Структурований паралелізм — жодних втрачених помилок, жодних загублених завдань. У сучасному коді віддавайте перевагу цьому, ніж простому create_task.
Реальний приклад: скребок з обмеженою швидкістю
Усі чотири шаблони в 15 рядках: обмежений паралелізм (семафор), тайм-аути (wait_for), явні гілки помилок (без ковтання) і збирання для розгортання. Скопіюйте та вставте цю фігуру в будь-яке асинхронне завдання введення-виведення.
Async у Python має справжню криву навчання, але ментальна модель проста: один потік, кооперативні паузи на await, введення-виведення накладається. Опануйте gather + Semaphore + wait_for + TaskGroup, і ви покриєте 95% асинхронної роботи, яку коли-небудь напишете.
Наступний крок: Senior Python track містить 20 практичних уроків з asyncio, TaskGroup, aiohttp/httpx і наведених вище шаблонів паралелізму — зі справжнім Python, запущеним у вашому браузері.