JavaRush /Курси /Модуль 4: FastAPI /Асинхронні концепції в Python (async, await)

Асинхронні концепції в Python (async, await)

Модуль 4: FastAPI
Рівень 2 , Лекція 0
Відкрита

Фреймворк FastAPI повністю асинхронний, тож для початку згадаймо, що таке асинхронне програмування і як воно працює...

Уяви, що ти шеф-кухар у ресторані і готуєш кілька страв одночасно. Поставивши суп на повільний вогонь, ти не стоїш біля каструлі 30 хвилин, спостерігаючи, як він кипить. Замість цього ти використовуєш цей час для нарізки овочів або приготування соусу для іншої страви.

Точно так само працює асинхронне програмування. Коли твій код запускає операцію, яка займає час (наприклад, запит до бази даних або зовнішнього API), він не "замирає" в очікуванні відповіді. Натомість він позначає, що треба повернутися до цього завдання пізніше, і тим часом виконує інші корисні задачі. Коли відповідь готова, код повертається і продовжує роботу з отриманими даними.

Ключові слова async і await в Python дозволяють організувати такий підхід: async позначає функцію, яка може "перемикатися" між задачами, а await — точку, де функція може призупинитися і поступитися контролем іншим задачам, поки чекає завершення якоїсь операції.

Асинхронне програмування — це підхід, коли задачі, що потребують багато часу на обробку, такі як запити до бази даних або взаємодія з веб-сервісами, не блокують виконання решти коду. Це підвищує продуктивність і відзивчивість застосунків.

Синхронне vs асинхронне виконання коду

У синхронному коді кожна операція виконується послідовно. Якщо одна з них займає багато часу, виконання програми "заморожується". Асинхронний код дозволяє запускати інші задачі, поки одна з операцій очікує завершення.

Приклад синхронного коду:


import time

def fetch_data():
    time.sleep(3)  # Емуляція довгої операції
    return "Дані отримані"

print("Починаємо завантаження даних...")
result = fetch_data()
print(result)

Вивід:


Починаємо завантаження даних...
(3 секунди очікування...)
Дані отримані

Приклад асинхронного коду:


import asyncio

async def fetch_data():
    await asyncio.sleep(3)  # Асинхронний "сніданок" або просто "сон"
    return "Дані отримані"

async def main():
    print("Починаємо завантаження даних...")
    result = await fetch_data()
    print(result)

asyncio.run(main())

Вивід:


Починаємо завантаження даних...
(3 секунди очікування без блокування інших задач)
Дані отримані

Асинхронні функції в Python

Асинхронне програмування в Python базується на двох ключових концепціях, про які ми вже встигли згадати: async і await. Давай розберемося в них детальніше.

Ключове слово async перед функцією вказує, що ця функція є асинхронною. Її виклик повертає спеціальний об'єкт — coroutine, а не результат одразу.


async def my_async_function():
    return "Привіт, асинхронний світ!"

# Виклик функції повертає coroutine
coro = my_async_function()
print(coro)  # <coroutine object my_async_function ...>

Ключове слово await використовується всередині асинхронних функцій для очікування виконання іншої coroutine. Воно "ставить на паузу" виконання поточної функції до завершення coroutine, звільняючи ресурси для інших задач.


import asyncio

async def my_async_function():
    await asyncio.sleep(1)  # Очікування 1 секунду
    return "Завершено!"

async def main():
    result = await my_async_function()
    print(result)

asyncio.run(main())

Важливе застереження

Асинхронні функції не можна викликати прямо в синхронному коді! Завжди використовуй asyncio.run() або інші підходи для запуску coroutine.


Розуміння Event Loop (цикл подій)

Event Loop (цикл подій) — це "диригент" асинхронного оркестру. Він керує виконанням усіх запущених coroutine, перемикаючись між ними, поки одна з них очікує результату.

Коли ти викликаєш asyncio.run(), Python створює event loop і запускає його. Це дозволяє програмі виконувати кілька задач одночасно.

Приклад роботи Event Loop

Поглянемо на простий приклад з кількома задачами:


import asyncio

async def task_one():
    print("Задача 1: Початок")
    await asyncio.sleep(2)
    print("Задача 1: Кінець")

async def task_two():
    print("Задача 2: Початок")
    await asyncio.sleep(1)
    print("Задача 2: Кінець")

async def main():
    await asyncio.gather(task_one(), task_two())

asyncio.run(main())

Вивід програми:


Задача 1: Початок
Задача 2: Початок
Задача 2: Кінець
Задача 1: Кінець

Тут await asyncio.gather() запускає обидві задачі одночасно. Event Loop перемикається між задачами, коли одна з них "спить".


Асинхронність у реальних сценаріях

Асинхронність найкорисніша в таких випадках:

  • Робота з зовнішніми API (наприклад, відправка HTTP-запитів)
  • Взаємодія з базами даних
  • Обробка файлових операцій

Покажемо приклад асинхронного HTTP-запиту. Використаємо бібліотеку httpx для відправки асинхронних запитів:


import httpx
import asyncio

async def get_data():
    async with httpx.AsyncClient() as client:
        response = await client.get('https://jsonplaceholder.typicode.com/todos/1')
        print(response.json())

asyncio.run(get_data())

Вивід програми:


{'userId': 1, 'id': 1, 'title': 'delectus aut autem', 'completed': False}

Типові помилки та підводні камені

Асинхронність у Python — це потужний інструмент, але й вона неідеальна. Ось проблеми, які часто трапляються у новачків:

  1. Спроба викликати асинхронну функцію в синхронному коді.

    Асинхронну функцію не можна запустити без event loop:

    
    async def my_function():
        return "Помилка"
    
    my_function()  # Помилка! Не можна викликати напряму.
    
    Замість цього використовуй:
    
    asyncio.run(my_function())
    
  2. Пропущене ключове слово await.

    Якщо ти забудеш await, coroutine не буде виконана (і Python навіть не пожурить):

    
    async def my_function():
        await asyncio.sleep(1)
        print("Привіт!")
    
    async def main():
        my_function()  # Немає await — нічого не відбудеться.
        await my_function()  # Все буде ок.
    
    asyncio.run(main())
    
  3. Блокуючий код в асинхронній функції.
    Якщо всередині асинхронної функції написати блокуючий код (наприклад, time.sleep() замість await asyncio.sleep()), це "заморозить" event loop.

Застосування асинхронності в FastAPI

Асинхронність в Python чудово поєднується з FastAPI. У наступній лекції ми створимо наш перший асинхронний endpoint з GET-запитом. Це дозволить твоєму API обробляти тисячі запитів одночасно, при цьому залишаючись швидким і відзивчивим.

Важливо пам'ятати, що кожна асинхронна функція, яку ти пишеш у FastAPI, виконується в рамках одного event loop, яким керує сервер Uvicorn.

На цьому етапі ти готовий розширювати свої знання з FastAPI, додаючи асинхронні операції в свої API. У наступній лекції ми зануримось у створення реальних асинхронних endpoint'ів.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ