asyncio Python
| Введение | |
| Простейший пример | |
| HTTP запросы | |
| Похожие статьи |
Введение
Установка
python -m venv venv
source venv/Scripts/activate
touch requirements.txt
vi requirements.txt
python -m pip install --upgrade pip
python -m pip install -r requirements.txt
Пример
import time def brew_coffee(): print("Start brew_coffee()") time.sleep(3) print("End brew_coffee()") return "Coffee ready" def toast_bagel(): print("Start toast_bagel()") time.sleep(2) print("End toast_bagel()") return "Bagel ready" def main(): start_time = time.time() result_coffee = brew_coffee() result_bagel = toast_bagel() end_time = time.time() elapsed_time = end_time - start_time print(f"Result of brew_coffee is: {result_coffee}") print(f"Result of toast_bagel is: {result_bagel}") print(f"Elapsed time: {elapsed_time}") if __name__ == "__main__": main()
Start brew_coffee() End brew_coffee() Start toast_bagel() End toast_bagel() Result of brew_coffee is: Coffee ready Result of toast_bagel is: Bagel ready Elapsed time: 5.0012452602386475 Process finished with exit code 0
import asyncio import time async def brew_coffee(): print("Start brew_coffee()") time.sleep(3) print("End brew_coffee()") return "Coffee ready" async def toast_bagel(): print("Start toast_bagel()") time.sleep(2) print("End toast_bagel()") return "Bagel ready" def main(): start_time = time.time() result_coffee = brew_coffee() result_bagel = toast_bagel() end_time = time.time() elapsed_time = end_time - start_time print(f"Result of brew_coffee is: {result_coffee}") print(f"Result of toast_bagel is: {result_bagel}") print(f"Elapsed time: {elapsed_time}") if __name__ == "__main__": main()
Result of brew_coffee is: <coroutine object brew_coffee at 0x00000202D448D240> Result of toast_bagel is: <coroutine object toast_bagel at 0x00000202D448E5C0> Elapsed time: 0.0 C:\Users\Andrei\py\async_ex.py:36: RuntimeWarning: coroutine 'brew_coffee' was never awaited main() RuntimeWarning: Enable tracemalloc to get the object allocation traceback C:\Users\Andrei\py\async_ex.py:36: RuntimeWarning: coroutine 'toast_bagel' was never awaited main() RuntimeWarning: Enable tracemalloc to get the object allocation traceback
Используем asyncio.gather() чтобы запустить задания асинхронно
import asyncio import time async def brew_coffee(): print("Start brew_coffee()") # time.sleep is not awaitable function await asyncio.sleep(3) print("End brew_coffee()") return "Coffee ready" async def toast_bagel(): print("Start toast_bagel()") await asyncio.sleep(2) print("End toast_bagel()") return "Bagel ready" async def main(): start_time = time.time() batch = asyncio.gather(brew_coffee(), toast_bagel()) result_coffee, result_bagel = await batch end_time = time.time() elapsed_time = end_time - start_time print(f"Result of brew_coffee is: {result_coffee}") print(f"Result of toast_bagel is: {result_bagel}") print(f"Elapsed time: {elapsed_time}") if __name__ == "__main__": asyncio.run(main())
Start brew_coffee() Start toast_bagel() End toast_bagel() End brew_coffee() Result of brew_coffee is: Coffee ready Result of toast_bagel is: Bagel ready Elapsed time: 3.0193281173706055
Альтернативный способ
async def main(): start_time = time.time() # batch = asyncio.gather(brew_coffee(), toast_bagel()) # result_coffee, result_bagel = await batch coffee_task = asyncio.create_task(brew_coffee()) toast_task = asyncio.create_task(toast_bagel()) result_coffee = await coffee_task result_bagel = await toast_task end_time = time.time() elapsed_time = end_time - start_time print(f"Result of brew_coffee is: {result_coffee}") print(f"Result of toast_bagel is: {result_bagel}") print(f"Elapsed time: {elapsed_time:.2f} seconds")
HTTP запросы
В файле req_http.py сохраним библиотеку из двух функций для выполнения синхронных и асинхронных запросов с помощью requests
# req_http.py import asyncio import requests # A few handy JSON types JSON = int | str | float | bool | None | dict[str, "JSON"] | list["JSON"] JSONObject = dict[str, JSON] JSONList = list[JSON] def http_get_sync(url: str) -> JSONObject: response = requests.get(url) return response.json() async def http_get(url: str) -> JSONObject: return await asyncio.to_thread(http_get_sync, url)
В дальнейших примерах будем использовать сперва синхронную функцию http_get_sync(), а затем и асинхронную http_get()
Получим с pokeapi.co имя случайного покемона. Сперва синхронным способом
from random import randint from req_http import http_get_sync # The highest Pokemon id MAX_POKEMON = 898 def get_random_pokemon_name_sync() -> str: pokemon_id = randint(1, MAX_POKEMON) pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" pokemon = http_get_sync(pokemon_url) return str(pokemon["name"]) def main() -> None: pokemon_name = get_random_pokemon_name_sync() print(pokemon_name) if __name__ == "__main__": main()
swanna
Асинхронный вариант тех же действий
import asyncio from random import randint from req_http import http_get # The highest Pokemon id MAX_POKEMON = 898 async def get_random_pokemon_name() -> str: pokemon_id = randint(1, MAX_POKEMON) pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" pokemon = await http_get(pokemon_url) return str(pokemon["name"]) async def main() -> None: pokemon_name = await get_random_pokemon_name() print(pokemon_name) if __name__ == "__main__": asyncio.run(main())
bronzong
В данном примере делается всего один запрос, поэтому разницу увидеть нельзя.
Выполним по 20 запросов. Оставим асинхронный синтаксис, но в первом случае просто запустим
цикл for … in
а во втором случае используем asyncio.gather()
В
предыдущем примере
у нас было два разных именованных задания, поэтому мы перечисляли их явно
batch = asyncio.gather(brew_coffee(), toast_bagel())
Сейчас одно задание нужно асинхронно запустить 20 раз поэтому мы воспользуемся генератором списка
import asyncio from random import randint from time import perf_counter from req_http import http_get # The highest Pokemon id MAX_POKEMON = 898 async def get_random_pokemon_name() -> str: pokemon_id = randint(1, MAX_POKEMON) pokemon_url = f"https://pokeapi.co/api/v2/pokemon/{pokemon_id}" pokemon = await http_get(pokemon_url) return str(pokemon["name"]) async def main() -> None: # 20 calls without real async time_before = perf_counter() for _ in range(20): pokemon_name = await get_random_pokemon_name() print(pokemon_name) print(f"Total time (synchronous): {perf_counter() - time_before:.2f}.") # 20 calls with async time_before = perf_counter() result = await asyncio.gather(*[get_random_pokemon_name() for _ in range(20)]) print(result) print(f"Total time (asynchronous): {perf_counter() - time_before:.2f}.") if __name__ == "__main__": asyncio.run(main())
cleffa timburr gothita wynaut wynaut trumbeak audino togedemaru dedenne necrozma cramorant gulpin registeel lileep houndoom luvdisc tympole dratini braixen floatzel Total time (synchronous): 9.47. ['dhelmise', 'articuno', 'ferroseed', 'rillaboom', 'vanillite', 'darkrai', 'hatterene', 'machamp', 'charjabug', 'dreepy', 'pineco', 'ralts', 'liepard', 'greedent', 'ferrothorn', 'urshifu-single-strike', 'combee', 'taillow', 'sandaconda', 'falinks'] Total time (asynchronous): 2.53.
Выигрыш во времени составил почти семь секунд
Article author: Andrei Olegovich
| FastAPI | |
| list comprehension: Абстракция списка | |
| Python | |
| Pydantic | |
| Пример API с БД | |
| Циклы | |
| multiprocessing |