Асинхронный Python в веб-разработке | Asyncio и Aiohttp
Асинхронное программирование хорошо подходит для задач, которые включают частое чтение и запись файлов или отправку данных с сервера туда и обратно. Асинхронные программы выполняют операции ввода-вывода неблокирующим образом, а это означает, что они могут выполнять другие задачи, ожидая возврата данных от клиента, а не просто ждать, тратя впустую ресурсы и время.
Python, как и многие другие языки, по умолчанию не является асинхронным. К счастью, быстрые изменения в мире ИТ позволяют нам писать асинхронный код даже на языках, которые изначально для этого не предназначались.
Неблокирующее поведение асинхронных программ может привести к значительному повышению производительности в контексте веб-приложения, помогая решить проблему разработки реактивных приложений.
В Python 3 встроено несколько мощных инструментов для написания асинхронных приложений. В этой статье мы рассмотрим некоторые из этих инструментов.
Введение в асинхронный Python
Для тех, кто знаком с написанием традиционного кода Python, переход к асинхронному коду может быть концептуально немного сложным. Асинхронный код в Python опирается на сопрограммы(coroutines), которые в сочетании с циклом обработки событий позволяют писать код, который может выполнять несколько действий одновременно.
Сопрограммы можно рассматривать как функции, в коде которых есть точки, в которых они возвращают управление программой вызывающему контексту. Эти точки позволяют приостанавливать и возобновлять выполнение сопрограммы в дополнение к обмену данными между контекстами.
Цикл событий решает, какой фрагмент кода выполняется в любой момент — он отвечает за приостановку, возобновление и взаимодействие между сопрограммами. Это означает, что части разных сопрограмм могут выполняться не в том порядке, в котором они были запланированы. Эта идея выполнения разных фрагментов кода не по порядку называется параллелизмом.
Размышление о параллелизме в контексте выполнения HTTP
запросов может многое прояснить. Представьте, что вы хотите сделать много независимых запросов к серверу. Например, мы могли бы запросить веб-сайт, чтобы получить статистику обо всех спортивных игроках в данном сезоне.
Мы могли бы сделать каждый запрос последовательно. Однако мы можем представить, что с каждым запросом наш код может потратить некоторое время на ожидание доставки запроса на сервер и отправки ответа обратно.
Иногда эти операции могут занимать даже несколько секунд. Приложение может испытывать задержки в сети из-за большого количества пользователей или просто из-за ограничений скорости данного сервера.
Что, если бы наш код мог делать другие вещи, ожидая ответа от сервера? Более того, что, если он вернется к обработке данного запроса только после получения данных ответа? Мы могли бы сделать много запросов в быстрой последовательности, если бы нам не приходилось ждать завершения каждого отдельного запроса, прежде чем переходить к следующему в списке.
Сопрограммы с циклом событий позволяют нам писать код, который ведет себя именно таким образом.
Asyncio
asyncio , часть стандартной библиотеки Python, предоставляет цикл обработки событий и набор инструментов для управления им. С помощью asyncio мы можем планировать выполнение сопрограмм и создавать новые сопрограммы (на самом деле asyncio.Task
объекты, используя терминологию asyncio ), которые завершат выполнение только после завершения выполнения составных сопрограмм.
В отличие от других языков асинхронного программирования, Python не заставляет нас использовать цикл обработки событий, который поставляется вместе с языком. Сопрограммы Python представляют собой асинхронный API, с помощью которого мы можем использовать любой цикл обработки событий.
Существуют проекты, которые реализуют совершенно другой цикл событий, например curio , или позволяют использовать другую политику цикла событий для asyncio (политика цикла событий — это то, что управляет циклом событий «за кулисами»), например uvloop .
Давайте взглянем на фрагмент кода, который одновременно запускает две сопрограммы, каждая из которых выводит сообщение через одну секунду:
# example1.py import asyncio async def wait_around(n, name): for i in range(n): print(f"{name}: iteration {i}") await asyncio.sleep(1.0) async def main(): await asyncio.gather(*[ wait_around(2, "coroutine 0"), wait_around(5, "coroutine 1") ]) loop = asyncio.get_event_loop() loop.run_until_complete(main())
me@local:~$ time python example1.py coroutine 1: iteration 0 coroutine 0: iteration 0 coroutine 1: iteration 1 coroutine 0: iteration 1 coroutine 1: iteration 2 coroutine 1: iteration 3 coroutine 1: iteration 4 real 0m5.138s user 0m0.111s sys 0m0.019s
Этот код выполняется примерно за 5 секунд, поскольку asyncio.sleep
сопрограмма устанавливает точки, в которых цикл обработки событий может перейти к выполнению другого кода. Более того, мы указали циклу событий запланировать оба wait_around
экземпляра для одновременного выполнения с asyncio.gather
функцией.
Asyncio.gather
берет список «ожидаемых» (т. е. сопрограмм или asyncio.Task
объектов) и возвращает один asyncio.Task
объект, который завершается только после завершения всех составляющих его задач/сопрограмм. Последние две строки являются asyncio
шаблоном для запуска данной сопрограммы до ее завершения.
Сопрограммы, в отличие от функций, не начинают выполняться сразу после вызова. Ключевое await
слово — это то, что говорит циклу обработки событий запланировать выполнение сопрограммы.
Если мы уберем await
перед asyncio.sleep
, программа завершится (почти) мгновенно, так как мы не сказали циклу обработки событий фактически выполнить сопрограмму, что в данном случае говорит сопрограмме приостановить работу на заданное время.
Поняв, как выглядит асинхронный код Python, давайте перейдем к асинхронной веб-разработке.
Установка aiohttp
aiohttp — это библиотека Python для выполнения асинхронных HTTP
запросов. Кроме того, он предоставляет основу для сборки серверной части веб-приложения. Используя Python 3.5+ и pip, мы можем установить aiohttp :
pip install --user aiohttp
Клиентская сторона: создание запросов
В следующих примерах показано, как мы можем загрузить HTML-контент веб-сайта «example.com» с помощью aiohttp :
# example2_basic_aiohttp_request.py import asyncio import aiohttp async def make_request(): url = "https://example.com" print(f"making request to {url}") async with aiohttp.ClientSession() as session: async with session.get(url) as resp: if resp.status == 200: print(await resp.text()) loop = asyncio.get_event_loop() loop.run_until_complete(make_request())
Несколько вещей, которые следует подчеркнуть:
- Как и with,
await asyncio.sleep
мы должны использоватьawait
withresp.text()
, чтобы получить HTML-контент страницы. Если бы мы его опустили, вывод нашей программы был бы примерно таким:
me@local:~$ python example2_basic_aiohttp_request.py <coroutine object ClientResponse.text at 0x7fe64e574ba0>
async with
— менеджер контекста, который работает с сопрограммами вместо функций. В обоих случаях, когда он используется, мы можем представить себе, что внутри aiohttp закрывает соединения с серверами или иным образом освобождает ресурсы.aiohttp.ClientSession
имеет методы, соответствующие HTTP- глаголам. Таким же- образом, как и
session.get
запрос GET ,session.post
будет выполнен запрос POST .
Этот пример сам по себе не дает преимущества в производительности по сравнению с синхронными HTTP-запросами. Настоящая красота aiohttp на стороне клиента заключается в выполнении нескольких одновременных запросов:
# example3_multiple_aiohttp_request.py import asyncio import aiohttp async def make_request(session, req_n): url = "https://example.com" print(f"making request {req_n} to {url}") async with session.get(url) as resp: if resp.status == 200: await resp.text() async def main(): n_requests = 100 async with aiohttp.ClientSession() as session: await asyncio.gather( *[make_request(session, i) for i in range(n_requests)] ) loop = asyncio.get_event_loop() loop.run_until_complete(main())
Вместо того, чтобы делать каждый запрос последовательно, мы просим asyncio
делать их одновременно с asycio.gather
.
Заключение
В этой статье мы рассмотрели, как выглядит асинхронная веб-разработка на Python, ее преимущества и способы использования.