Книга: Простой Python. Современный стиль программирования. 2-е изд.
Назад: Приложение Б. Установка Python 3
Дальше: Приложение Г. Ответы к упражнениям

Приложение В. Нечто совершенно иное: async

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

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

Ваш ЦП может совершать только одну операцию за раз, поэтому синхронное выполнение целесообразно. Но оказывается, зачастую программы не выполняют свой код, а ждут чего-то наподобие получения данных из файла или отклика сетевого сервиса. Процесс похож на то, как мы открываем окно браузера и ждем загрузки сайта. Если бы в наших силах было избежать этого «активного ожидания», то мы могли бы сократить общее время выполнения наших программ. Это также называется улучшением пропускной способности.

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

Сопрограммы и циклы событий

В версию Python 3.4 был добавлен асинхронный модуль asyncio. Затем в версию Python 3.5 были добавлены ключевые слова async и await. Все это позволяет реализовать следующие новые концепции:

сопрограммы — это функции, которые приостанавливаются в разных точках;

цикл событий — цикл, в котором планируется запуск сопрограмм, а также они выполняются.

Это позволяет писать асинхронный код, выглядящий как обычный, синхронный, к которому мы привыкли. В противном случае следует использовать один из методов, описанных в главах 15 и 17, они подытожены ниже, в разделе «async против…» данного приложения.

Многозадачность — это то, что ваша операционная система делает с вашими процессами. Она решает, какое действие справедливо, кто занимает процессор, когда выполнять ввод/вывод данных и т.д. Цикл событий, однако, предоставляет возможность кооперативной многозадачности, при которой сопрограммы указывают, когда готовы стартовать и останавливаться. Они работают в одном потоке, поэтому вы не столкнетесь с потенциальными проблемами, описанными в подразделе «Потоки» на с. 311.

Сопрограмма определяется с помощью ключевого слова async, которое помещается перед def. Она вызывается следующим образом:

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

• можно также использовать конструкцию asyncio.run(), которая явно запускает цикл обработки событий;

помимо этого, можно воспользоваться вызовами asyncio.create_task() или asyncio.ensure_future().

В этом примере показаны первые два метода вызова сопрограмм:

>>> import asyncio

>>>

>>> async def wicked():

...     print("Surrender,")

...     await asyncio.sleep(2)

...     print("Dorothy!")

...

>>> asyncio.run(wicked())

Surrender,

Dorothy!

В процессе выполнения программы произошла драматичная пауза длиной 2 секунды, которую вы не можете увидеть на печатной странице. Докажу, что я не жульничал (подробности работы модуля timeit см. в главе 19):

>>> from timeit import timeit

>>> timeit("asyncio.run(wicked())", globals=globals(), number=1)

Surrender,

Dorothy!

2.005701574998966

Вызов asyncio.sleep(2) сам по себе — сопрограмма, которую мы использовали в качестве примера некой времязатратной операции, такой как вызов API.

Строка asyncio.run(wicked()) — это способ запуска сопрограммы из синхронного кода Python (на верхнем уровне программы).

Отличие от стандартного синхронного вызова (с использованием time.sleep()) заключается в том, что вызывающая сторона метода wicked() не блокируется на 2 секунды, пока работает метод.

Третий способ запустить сопрограмму — создать задачу и использовать ключевое слово await. Этот подход показан в следующем примере наряду с двумя предыдущими методами:

>>> import asyncio

>>>

>>> async def say(phrase, seconds):

...     print(phrase)

...     await asyncio.sleep(seconds)

...

>>> async def wicked():

...     task_1 = asyncio.create_task(say("Surrender,", 2))

...     task_2 = asyncio.create_task(say("Dorothy!", 0))

...     await task_1

...     await task_2

...

>>> asyncio.run(wicked())

Surrender,

Dorothy!

Запустив данный код, вы увидите: между выводом на экран двух строк нет задержки. Это произошло потому, что у нас есть две отдельные задачи. Первая — task_1 — приостановилась на 2 секунды после вывода слова Surrender, но это не повлияло на вторую — task_2.

Ключевое слово await аналогично ключевому слову yield в генераторах, но вместо того, чтобы возвращать значение, оно помечает место, где цикл обработки событий может приостановиться, если нужно.

В документации () содержится гораздо больше информации. Синхронный и асинхронный код могут сосуществовать в одной программе. Главное — помнить о необходимости указывать ключевое слово async перед def при объявлении функции и ключевое слово await перед вызовом асинхронной функции.

Для дополнительной информации см.:

список () ссылок на ПО, использующее asyncio;

код () веб-сканера, применяющего asyncio.

Альтернативы asyncio. Несмотря на то что модуль asyncio входит в стандартный пакет Python, вы можете использовать ключевые слова async и await без него. Сопрограммы и цикл обработки событий не зависят друг от друга. Дизайн модуля asyncio иногда подвергается критике (), и на этом фоне появились сторонние альтернативы:

curio (/);

trio (/).

Рассмотрим реальный пример, использующий trio и asks (/) (асинхронный веб-фреймворк, созданный на основе requests API). В примере В.1 показывается веб-сканер, который использует конкурентность с помощью trio и asks, он был создан на основе ответа на stackoverflow (). Чтобы запустить этот пример, сначала установите trio и asks с помощью pipinstall.

Пример В.1. trio_asks_sites.py

import time

 

import asks

import trio

 

asks.init("trio")

 

urls = [

    '/',

    '/',

    '/',

    '',

]

 

async def get_one(url, t1):

    r = await asks.get(url)

    t2 = time.time()

    print(f"{(t2-t1):.04}\t{len(r.content)}\t{url}")

 

async def get_sites(sites):

    t1 = time.time()

    async with trio.open_nursery() as nursery:

        for url in sites:

            nursery.start_soon(get_one, url, t1)

 

if __name__ == "__main__":

    print("seconds\tbytes\turl")

    trio.run(get_sites, urls)

Вот что я получил:

$ python trio_asks_sites.py

seconds bytes   url

0.1287  5735    /

0.2134  146082  

0.215   11029   /

0.3813  52385   /

Вы увидите, что trio вызывает не asyncio.run(), а собственный метод trio.open_nursery(). Если вам интересно, то можете прочесть эссе () и обсу­ждение () решений, связанных с дизайном trio.

Новый пакет, который называется AnyIO (), предоставляет единый интерфейс к asyncio, curio и trio.

В будущем можно ожидать появления новых асинхронных подходов как в стандартном Python, так и в сторонних библиотеках.

async против…

Как вы уже видели на протяжении этой книги, реализовать конкурентность можно с помощью множества приемов. Чем async отличается от них, а в чем похож?

Процессы. Это хорошее решение, если вы хотите использовать все ядра ЦП на вашей машине или даже несколько машин. Однако процессы довольно тяжелые, на их запуск требуется некоторое время, а для коммуникации между процессами следует применить сериализацию.

• Потоки. Несмотря на то что потоки разрабатывались как «легковесная» альтернатива процессам, каждый поток занимает большой объем памяти. Сопрограммы гораздо легче, чем потоки; вы можете создать сотни тысяч сопрограмм на машине, которая может поддерживать всего несколько тысяч потоков.

• «Зеленые» потоки. Такие «зеленые» потоки, как gevent, работают хорошо и выглядят очень похоже на синхронный код, но требуют выполнять «обезьяний патч» стандартных функций Python, например библиотек сокетов.

• Функции обратного вызова. Такие библиотеки, как twisted, полагаются на функции обратного вызова: они вызываются, когда происходит определенное событие. Это знакомо программистам, работающим с GUI и JavaScript.

Очереди. Как правило, являются крупномасштабным решением, когда вашим данным или процессам требуется больше одной машины.

Асинхронные фреймворки и серверы

Асинхронность появилась в Python недавно, и разработчикам требуется время для создания асинхронных версий таких фреймворков, как Flask.

Стандарт ASGI (/) — это асинхронная версия WSGI, более подробно обсуждается здесь: .

Ниже представлены некоторые веб-серверы, работающие по принципу ASGI:

hypercorn ();

• sanic (/);

uvicorn (/).

А вот некоторые асинхронные веб-фреймворки:

aiohttp (/) — клиент и сервер;

• api_hour ();

• asks (/) — похож на requests;

• blacksheep ();

• bocadillo ();

• channels (/);

• fastapi (/) — использует аннотации типов;

• muffin (/);

• quart ();

• responder (/);

• sanic (/);

• starlette (/);

• tornado (/);

vibora (/).

И наконец, несколько асинхронных интерфейсов к базам данных:

aiomysql (/);

• aioredis (/);

• asyncpg ().

Назад: Приложение Б. Установка Python 3
Дальше: Приложение Г. Ответы к упражнениям

dofermerdat
этот. породы кроликов с фотографиями мясныефермер ру форум рассадакрупнейшие сельскохозяйственные районы зарубежной европыкрс мясо