Учебное пособие по Python async / await

Асинхронное программирование набирает обороты в последние несколько лет, и не зря. Хотя это может быть сложнее, чем традиционный линейный стиль, он также намного эффективнее. Например, вместо ожидания завершения HTTP-запроса перед продолжением выполнения с помощью асинхронных сопрограмм Python вы можете отправить запрос и выполнить другую работу, ожидающую в очереди, пока ожидает завершения HTTP-запроса. Может потребоваться немного больше размышлений, чтобы понять логику,

Асинхронное программирование набирает обороты в последние несколько лет, и не зря. Хотя это может быть сложнее, чем традиционный линейный стиль, он также намного эффективнее.

Например, вместо ожидания завершения HTTP-запроса перед продолжением выполнения с помощью асинхронных сопрограмм Python вы можете отправить запрос и выполнить другую работу, ожидающую в очереди, пока ожидает завершения HTTP-запроса. Чтобы понять правильную логику, может потребоваться немного больше размышлений, но вы сможете справиться с гораздо большей работой с меньшими ресурсами.

Даже тогда синтаксис и выполнение асинхронных функций в таких языках, как Python, на самом деле не так уж и сложны. Другое дело, с JavaScript, но Python, похоже, выполняет его довольно хорошо.

Асинхронность, кажется, главная причина того, почему Node.js так популярен для программирования на стороне сервера. Большая часть кода, который мы пишем, особенно в тяжелых приложениях ввода-вывода, таких как веб-сайты, зависит от внешних ресурсов. Это может быть что угодно, от удаленного вызова базы данных до POSTing в службу REST. Как только вы запрашиваете какой-либо из этих ресурсов, ваш код ждет, ничего не делая.

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

Сопрограммы

Асинхронная функция в Python обычно называется сопрограммой, которая представляет собой просто функцию, использующую ключевое слово async @asyncio.coroutine . Любая из приведенных ниже функций будет работать как сопрограмма и фактически эквивалентна по типу:

 import asyncio 
 
 async def ping_server(ip): 
 pass 
 
 @asyncio.coroutine 
 def load_file(path): 
 pass 

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

На случай, если вам когда-нибудь понадобится определить, является ли функция сопрограммой или нет, asyncio предоставляет метод asyncio.iscoroutinefunction(func) который делает именно это за вас. Или, если вам нужно определить, является ли объект, возвращаемый функцией , объектом сопрограммы, вы можете вместо этого asyncio.iscoroutine(obj)

Доходность от

Есть несколько способов вызвать сопрограмму, один из которых - метод yield from . Это было введено в Python 3.3 и было улучшено в Python 3.5 в форме async/await (о которой мы поговорим позже).

yield from может использоваться следующим образом:

 import asyncio 
 
 @asyncio.coroutine 
 def get_json(client, url): 
 file_content = yield from load_file('/Users/scott/data.txt') 

Как видите, yield from используется в функции, украшенной @asyncio.coroutine . Если бы вы попытались использовать yield from извне этой функции, то получили бы такую ошибку от Python:

 File "main.py", line 1 
 file_content = yield from load_file('/Users/scott/data.txt') 
 ^ 
 SyntaxError: 'yield' outside function 

Чтобы использовать этот синтаксис, он должен находиться в другой функции (обычно с декоратором сопрограммы).

Асинхронный / ожидание

Более новый и чистый синтаксис заключается в использовании ключевых слов async/await Представленный в Python 3.5, async используется для объявления функции как сопрограммы, подобно тому, как это делает декоратор @asyncio.coroutine Его можно применить к функции, поместив его перед определением:

 async def ping_server(ip): 
 # ping code here... 

Чтобы вызвать эту функцию, мы используем await вместо yield from , но во многом таким же образом:

 async def ping_local(): 
 return await ping_server('192.168.1.1') 

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

В Python 3.5 поддерживаются оба способа вызова сопрограмм, но async/await должен быть основным синтаксисом.

Запуск цикла событий

Ни один из описанных выше сопрограмм не будет иметь значения (или работать), если вы не знаете, как запускать и запускать цикл событий . Цикл событий - это центральная точка выполнения асинхронных функций, поэтому, когда вы действительно хотите выполнить сопрограмму, это то, что вы будете использовать.

Цикл событий предоставляет вам несколько функций:

  • Регистрация, выполнение и отмена отложенных вызовов (асинхронные функции)
  • Создавайте клиентские и серверные транспорты для связи
  • Создавать подпроцессы и транспорты для связи с другой программой
  • Делегировать вызовы функций пулу потоков

Хотя на самом деле существует довольно много конфигураций и типов циклов событий, которые вы можете использовать, большинству программ, которые вы пишете, просто нужно будет использовать что-то вроде этого для планирования функции:

 import asyncio 
 
 async def speak_async(): 
 print('OMG asynchronicity!') 
 
 loop = asyncio.get_event_loop() 
 loop.run_until_complete(speak_async()) 
 loop.close() 

Последние три строки - это то, что нас здесь интересует. Он начинается с получения цикла событий по умолчанию ( asyncio.get_event_loop() ), планирования и выполнения задачи async, а затем закрытия цикла, когда цикл завершается.

Функция loop.run_until_complete() фактически блокирует, поэтому она не вернется, пока не будут выполнены все асинхронные методы. Поскольку мы выполняем это только в одном потоке, он не может двигаться вперед, пока цикл выполняется.

Теперь вы можете подумать, что это не очень полезно, поскольку мы все равно блокируем цикл событий (а не только вызовы ввода-вывода), но представьте, что вы обертываете всю свою программу в асинхронной функции, которая затем позволит вам запускать много асинхронных запросы одновременно, как на веб-сервере.

Вы даже можете прервать цикл обработки событий в отдельный поток, позволяя ему обрабатывать все длинные запросы ввода-вывода, в то время как основной поток обрабатывает логику программы или пользовательский интерфейс.

Пример

Хорошо, давайте посмотрим на более крупный пример, который мы действительно можем запустить. Следующий код представляет собой довольно простую асинхронную программу, которая извлекает JSON из Reddit, анализирует JSON и распечатывает самые популярные сообщения дня из / r / python, / r / programming и / r / compsci.

Первый показанный метод, get_json() , вызывается get_reddit_top() и просто создает HTTP-запрос GET на соответствующий URL-адрес Reddit. Когда это вызывается с помощью await , цикл событий может продолжаться и обслуживать другие сопрограммы, ожидая ответа HTTP. Как только это произойдет, JSON возвращается в get_reddit_top() , анализируется и распечатывается.

 import signal 
 import sys 
 import asyncio 
 import aiohttp 
 import json 
 
 loop = asyncio.get_event_loop() 
 client = aiohttp.ClientSession(loop=loop) 
 
 async def get_json(client, url): 
 async with client.get(url) as response: 
 assert response.status == 200 
 return await response.read() 
 
 async def get_reddit_top(subreddit, client): 
 data1 = await get_json(client, 'https://www.reddit.com/r/' + subreddit + '/top.json?sort=top&t=day&limit=5') 
 
 j = json.loads(data1.decode('utf-8')) 
 for i in j['data']['children']: 
 score = i['data']['score'] 
 title = i['data']['title'] 
 link = i['data']['url'] 
 print(str(score) + ': ' + title + ' (' + link + ')') 
 
 print('DONE:', subreddit + '\n') 
 
 def signal_handler(signal, frame): 
 loop.stop() 
 client.close() 
 sys.exit(0) 
 
 signal.signal(signal.SIGINT, signal_handler) 
 
 asyncio.ensure_future(get_reddit_top('python', client)) 
 asyncio.ensure_future(get_reddit_top('programming', client)) 
 asyncio.ensure_future(get_reddit_top('compsci', client)) 
 loop.run_forever() 

Это немного отличается от примера кода, который мы показали ранее. Чтобы запустить несколько сопрограмм в цикле событий, мы используем asyncio.ensure_future() а затем запускаем цикл навсегда, чтобы обработать все.

Чтобы запустить это, вам нужно aiohttp , что вы можете сделать с помощью PIP:

 $ pip install aiohttp 

Теперь просто убедитесь, что вы запускаете его с Python 3.5 или выше, и вы должны получить следующий результат:

 $ python main.py 
 46: Python async/await Tutorial (http://stackabuse.com/python-async-await-tutorial/) 
 16: Using game theory (and Python) to explain the dilemma of exchanging gifts. Turns out: giving a gift probably feels better than receiving one... (http://vknight.org/unpeudemath/code/2015/12/15/The-Prisoners-Dilemma-of-Christmas-Gifts/) 
 56: Which version of Python do you use? (This is a poll to compare the popularity of Python 2 vs. Python 3) (http://strawpoll.me/6299023) 
 DONE: python 
 
 71: The Semantics of Version Control - Wouter Swierstra (http://www.staff.science.uu.nl/~swier004/Talks/vc-semantics-15.pdf) 
 25: Favorite non-textbook CS books (https://www.reddit.com/r/compsci/comments/3xag9e/favorite_nontextbook_cs_books/) 
 13: CompSci Weekend SuperThread (December 18, 2015) (https://www.reddit.com/r/compsci/comments/3xacch/compsci_weekend_superthread_december_18_2015/) 
 DONE: compsci 
 
 1752: 684.8 TB of data is up for grabs due to publicly exposed MongoDB databases (https://blog.shodan.io/its-still-the-data-stupid/) 
 773: Instagram's Million Dollar Bug? (http://exfiltrated.com/research-Instagram-RCE.php) 
 387: Amazingly simple explanation of Diffie-Hellman. His channel has tons of amazing videos and only a few views :( thought I would share! (https://www.youtube.com/watch?v=Afyqwc96M1Y) 
 DONE: programming 

Обратите внимание, что если вы запустите это несколько раз, порядок, в котором печатаются данные субреддита, изменится. Это связано с тем, что каждый из наших вызовов освобождает (передает) контроль над потоком, позволяя выполнить другой HTTP-вызов. То, что вернется первым, распечатывается первым.

Заключение

Хотя встроенные в Python асинхронные функции не так удобны, как JavaScript, это не значит, что вы не можете использовать их для интересных и эффективных приложений. Просто потратьте 30 минут, чтобы изучить его все входы и выходы, и вы лучше поймете, как вы можете интегрировать это в свои собственные приложения.

Что вы думаете об async / await в Python? Как вы использовали это в прошлом? Дайте нам знать об этом в комментариях!

comments powered by Disqus