Особенности асинхронности в Python

от автора

Привет, Хабр! Меня зовут Николай Нагорный, я работаю в Росбанке над платформой Advisors’ Axiom. В этом посте я подробно расскажу о важной фиче, которая появилась в Python 3.5 — асинхронности. Затрону основные концепции и инструменты, приведу примеры кода. Пост будет полезен новичкам для понимания основ асинхронности и, может, даже опытным разработчикам в поиске новых идей и подходов.

Начнем с определения. Асинхронность — это парадигма программирования, которая позволяет выполнять несколько задач одновременно, не дожидаясь завершения каждой из них; важный инструмент для решения проблем с производительностью в веб-приложениях и серверных технологиях. С момента появления асинхронности в Python сразу было доступно несколько способов реализации асинхронного кода. Чаще всего для асинхронных операций в Python используют библиотеки async/await и asyncio. Также асинхронность может быть использована для веб-приложений в сочетании с фреймворками, например с Django, Flask или Fast API.

Асинхронность в чистом Python

Один из способов реализации асинхронности в Python — декоратор @asyncio.coroutine. Вот функция-корутина, которая выполняет ожидание в течение некоторого времени: 

import asyncio # импорт библиотеки  @asyncio.coroutine # декоратор def my_coroutine(seconds):    print ('Starting coroutine')    yield from asyncio.sleep(seconds) # возвращаем результат    print ('Finishing coroutine') loop = asyncio.get_event_loop () # создаем объект loop.run_until_complete(my_coroutine (2)) # запускаем loop.close () # закрываем

Другой, более предпочтительный способ появился в языке позднее — это асинхронные функции (Async Functions), которые вызываются через async. В примере ниже функция my_coroutine задана асинхронно, то есть программа не будет ждать завершения ее работы, а продолжит выполнение следующих команд:

import asyncio # импортируем библиотеку  async def my_async_function (seconds) : # создаем асинхронную функцию    print ('Starting async function')    await asyncio.sleep (seconds) # вызываем метод await для ожидания    print ('Finishing async function') loop = asyncio.get_event_loop () # создаем объект loop.run_until_complete(my_async_function (2)) # запускаем loop.close () # закрываем

Кроме того, в Python можно создавать асинхронные контексты с помощью ключевого слова async with. Это позволяет выполнять асинхронные операции внутри контекста, например открытие и закрытие файла: 

import asyncio # импортируем библиотеку  async def read_file (filename): # создаем асинхронную функцию    async with open(filename, 'r') as f: # открываем файл на чтение       contents = await f. read () # читаем весь файл       print (contents)

Модуль asyncio с методами async functions, async with, контексты и корутины — выбор средств асинхронности в Python неплохой. Комбинируя их, можно создавать программы с высокой параллельностью и улучшать их производительность, а также избегать блокировок программы во время длительных операций. Помните, что асинхронный код сам по себе более сложен для понимания и реализации, поэтому используйте его с умом. И тогда перечисленные инструменты раскроют весь свой немаленький потенциал.

Асинхронность во фреймворках Python — Django, FastAPI, Flask

Теперь поговорим об использовании асинхронности с популярными фреймворками — Django, Flask и FastAPI. Ни один из них не имеет встроенной поддержки асинхронности, но ее можно добавить с помощью внешних библиотек.

Для реализации асинхронных вызовов FastAPI использует стандартный модуль asyncio и поддерживает асинхронные функции нативно, на уровне ядра. У Django и Flask такой поддержки нет, но они тоже умеют работать с асинхронными библиотеками asyncio или aiohttp. Так что разогнать свои веб-приложения с помощью асинхронности использование этих фреймворков не помешает. Вот пример реализации с библиотекой aiohttp в Flask:

import aiohttp # импортируем библиотеку для работы с асинхронным примером from flask import Flask # импортируем библиотеку для работы с фреймворком app = Flask(__name__) # создаем приложение @app.route("/") # объявляем страницу async def main(): # создаем асинхронную функцию    async with aiohttp.ClientSession() as session: # открываем асинхронную клиентскую сессию       async with session.get ("https://www.example.com") as response: # используя сессию делаем асинхронный запрос          return response.text  if __name__ == Il “__main__”: # объявляем секцию main    app.run() # запускаем фреймворк по с настройками по умолчанию

Асинхронность в веб-приложениях лучше всего раскрывается при работе с длительными операциями или вызовами API. Но учтите, что так вы подписываетесь на более сложную отладку и поддержку.

Вот еще пример асинхронных функций в FastAPI: функция main использует asyncio.sleep для задержки выполнения на 10 секунд. Это может пригодиться, например, для ожидания ответа от внешнего API:

from fastapi import FastAPI # импортируем библиотеку для работы с фреймворком Aimport asyncio # импортируем библиотеку для работы с асинхронным примером app = FastAPI() # создаем приложение @app.get("/") # объявляем страницу async def main(): #создаем асинхронную функцию    await asyncio.sleep(10) # с помощью асинхронной библиотеки запускаем наш пример    return {"message": "Hello World"}   if __name__ == "__main__": # объявляем секцию main    uvicorn.run (app) # запускаем фреймворк по с настройками по умолчанию

Что касается Django, то он также поддерживает асинхронное программирование с помощью внешних библиотек Django Channels.

from channels.generic.websocket import AsyncWebsocketConsumer #библиотека для работы с асинхронностью   §class MyConsumer (AsyncWebsocketConsumer):     async def connect(self): # создаем асинхронный метод       await self.accept() # ожидаем       await asyncio.sleep (10) # выполняем действия       await self.send(text_data="Hello World") # отправляем данные       await self.close() # закрываем

В этом примере класс MyConsumer реализует асинхронное вебсокет-соединение через метод connect. Метод accept принимает соединение, а метод send отправляет сообщение клиенту. Для задержки отправки сообщения на 10 секунд используется функция asyncio.sleep. И в конце метод close закрывает соединение.

С помощью асинхронного программирования вы можете использовать все доступные ресурсы для обработки множества запросов одновременно, а не ставить их в очередь. Создаются асинхронные вьюхи в каждом фреймворке по-своему. В Django вы можете использовать декоратор async из библиотеки asyncio. Для Flask — декоратор asyncio.coroutine; Flask использует библиотеку gevent для управления асинхронными задачами. FastAPI имеет встроенную поддержку асинхронных функций, поэтому там можно использовать async def. Выбирайте фреймворк для работы с асинхронностью исходя из собственных задач и предпочтений. Я привел варианты выше, поскольку они точно предлагают удобный инструментарий для управления асинхронными задачами.

ORM — Object-Relational Mapping

ORM (Object-Relational Mapping) — это технология, которая позволяет связаться с базой данных, используя объекты Python, вместо того чтобы писать сырые SQL-запросы. Многие ORM для Python, такие как SQLAlchemy и Django ORM, поддерживают асинхронные версии. Библиотеки asyncio или asyncpg позволяют использовать асинхронные версии этих ORM в асинхронных приложениях.

С помощью ORM вы можете выполнять запросы к базе данных асинхронно, вместо того чтобы ждать ответа перед выполнением следующей задачи. Так вы можете увеличить производительность приложения и оптимизировать использование ресурсов. Пример асинхронного подключения к СУБД:

import asyncio # библиотека для работы с асинхронным кодом import asyncpg # библиотека для подключения к субд   async def main(): ## создаем асинхронную функцию    conn = await asyncpg.connect (user='user', password='password', database='database', host='host') # подключаем к субд    # создание таблицы     await conn.execute('''       CREATE TABLE IF NOT EXISTS test_table (          id serial PRIMARY KEY,          name text NOT NULL       )    ''')    # вставка данных в таблицу    await conn.execute('"       INSERT INTO test_table (name)       VALUES ($1)    ''', 'John Doe')    # выборка данных из таблицы    result = await conn.fetch('''       SELECT *       FROM test_table    ''')     print (result) # показываем результат    await conn.close() # закрываем соединение   asyncio.run(main())

В этом примере происходит соединение с базой данных, далее создание таблицы, вставка данных в таблицу и выборка данных. Все эти операции выполняются асинхронно, так что вы можете выполнять другие задачи одновременно с запросом к базе данных. Обработка запросов ускоряется и пользовательский опыт улучшается, так как во время ожидания ответа от базы данных приложение не проседает. Но еще раз напомню, что асинхронный код более сложен в реализации и отладке, поэтому оценивайте свои возможности и потребности проекта.

В нашей троице Django, Flask и FastAPI асинхронность в работе с базой данных можно реализовать через библиотеки asyncio и aiomysql. Они представляют собой асинхронные версии стандартных модулей для работы с базами данных, psycopg2 и mysql-python. Например в Django можно использовать asyncpg для работы PostgreSQL в асинхронном режиме. Соответственно, в Flask и FastAPI можно использовать aiomysql для MySQL.

Асинхронные ORM, такие как asyncio-orm или Tortoise-ORM, могут улучшить и производительность работы с базой данных в асинхронном режиме. В большинстве случаев асинхронный код позволяет использовать базу данных более эффективно за счет минимизации ожидания выполнения запросов. Покажу, как асинхронность может улучшить работу с базой данных, на примере с asyncio и aiomysql:

pip install aiomysql import asyncio # библиотека для работы с асинхронным кодом import aiomysql # библиотека для подключения к субд   async def main(): # создаем асинхронную функцию    conn = await aiomysql.connect(host='localhost', user='user',          password='password', db='dbname', charset='utf8mb4') # подключаем к субд    async with conn.cursor() as cursor: # открываем подключение          await cursor.execute ("SELECT * FROM table") # добавляем запрос          result = await cursor.fetchall() # получаем все из таблицы print(result) # показываем запрос   asyncio.run(main ())

Здесь мы создали простое приложение, которое подключается к базе данных и выполняет запрос. Используется асинхронный контекст-менеджер для управления соединением с базой данных, а затем выполняется SELECT-запрос и вывод результата.

Использование асинхронности в ORM может повысить производительность приложения, поскольку запросы к базе данных при этом выполняются в фоновом режиме, без блокировки потоков исполнения. Но помните о неминуемом усложнении кода и отладки. Django ORM и некоторые другие ORM поддерживают асинхронные запросы. Запросы эти не встроены в фундаментальный дизайн фреймворка и могут требовать дополнительных инструментов и настроек. С этой точки зрения проще использовать Flask с его asyncio.

Ограничения асинхронности

Не все базы данных поддерживают асинхронный режим работы — например реляционные базы данных, такие как PostgreSQL и MySQL. При использовании таких баз данных с асинхронным кодом вам могут потребоваться специальные библиотеки типа asyncpg.

Еще одно ограничение возникает при работе с транзакциями. Они обычно выполняются в блокирующем режиме, который заставляет все остальные запросы ждать, пока транзакция не будет завершена. Это может стать проблемой в асинхронных приложениях, так как они ожидают ответа в неблокирующем режиме. Здесь есть несколько решений: использование асинхронных ORM (asyncpg) или использование блокировок на уровне базы данных. Однако так вы будете вынуждены потерять в производительности и писать более сложный код.

Все три фреймворка, что мы рассматривали выше — FastAPI, Django и Flask — используют ORM (Object Relational Mapping) для управления базами данных. В ORM для работы с базами данных предусмотрены простые CRUD-операции (Create, Read, Update, Delete). В FastAPI и Flask ORM реализуют через сторонние библиотеки, такие как SQLAlchemy и Flask-SQLAlchemy. После этого с базами данных можно использовать и синхронные, и асинхронные операции (async for, async with, async/await). Django же с версии 3.0 поддерживает асинхронные операции с базой данных нативно, и вам тоже не придется ждать.

Заключение

Сегодня асинхронность очень важна для решения проблем с производительностью в веб-приложениях. В Python асинхронные функции можно организовать с помощью модуля asyncio. В фреймворках Django, Flask и FastAPI можно использовать асинхронные возможности для оптимизации работы с базами данных и выполнения сложных операций.

Следует помнить, что асинхронный стиль программирования усложнит код и отладку. И, конечно, потребует довольно глубокого понимания асинхронных принципов. Так что решение об использовании асинхронности должно быть осознанным и основываться на конкретных задачах.

Несмотря на это, асинхронность остается важным инструментом для улучшения производительности веб-приложений.


ссылка на оригинал статьи https://habr.com/ru/articles/736698/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *