原文連接:
http://stackabuse.com/python-async-await-tutorial/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.iojavascript
過去幾年,異步編程方式被愈來愈多的程序員使用, 固然這是有緣由的。 儘管異步編程比順序編程更難, 可是它也更高效。php
在順序編程中, 發起一個HTTP請求須要阻塞以等待他的返回結果, 使用異步編程你能夠發起這個HTTP請求, 而後在等待結果返回的同時作一些其餘的事情,等待結果的協程會被放在一個隊列裏面。 爲了保證邏輯的正確性, 這可能會須要考慮的更多, 可是這也使咱們用更少的資源處理更多的事情。
Python中的異步語法和調用並不難。 和Javascript的異步編程比起來可能有點難, 不過也還好。html
異步的處理方式可能解釋了爲何Node.js在服務器端這麼流行。 咱們的代碼依賴於外部的一些資源, 尤爲是IO密集型的應用, 好比網站可能須要從數據庫調用中向一個REST接口POST數據。 一旦咱們請求一些外部資源, 咱們的代碼就要阻塞, 而不能處理別的邏輯。
利用異步編程, 咱們能夠在等待其餘資源返回的時候, 作一些其餘的事情。java
在python中,異步函數被稱做協程: 使用async關鍵字 或者利用@asyncio.coroutine裝飾器。 下面的兩種形式是等效的:python
import asyncio async def ping_server(ip): pass @asyncio.coroutine def load_file(path): pass
上面的函數調用的時候返回的是一個協程的對象。 若是你熟悉javascript, 你能夠認爲這個返回對象就像javascript裏面的Promise。 如今調用這兩個函數, 是不能執行的, 僅僅返回的是一個協程對象, 這個對象能夠被用來在後面的event loop中使用。程序員
若是你想知道一個函數是否是協程, asyncio提供的asyncio.iscoroutine(obj)函數能夠幫助你。web
有幾種調用協程的方式,其中一種是使用yield from方法。 yield from在Python3.3中被引進, 在Python3.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, 你會獲得下面的錯誤:編程
File "main.py", line 1 file_content = yield from load_file('/Users/scott/data.txt') ^ SyntaxError: 'yield' outside function
必須在函數中使用yield from, 典型的用法是在有@asyncio.coroutine裝飾器的函數種使用。json
更新、更方便的語法是使用async/await關鍵字。async關鍵字是在Python3.5引入的, 被用來修飾一個函數, 讓其成爲協程, 和@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')
你不能在一個協程外面使用await關鍵字, 不然會獲得語法錯誤。 就像yield from不能在函數外面使用同樣。
Python3.5中, 上面兩種協程聲明的方式都支持, 可是首選async/await方式。
上面描述的協程例子都不會正常的運行, 若是要運行, 須要用到event loop.。event loop是協程執行的控制點, 若是你但願執行協程, 就須要用到它們。
event loop提供了以下的特性:
註冊、執行、取消延時調用(異步函數)
建立用於通訊的client和server協議(工具)
建立和別的程序通訊的子進程和協議(工具)
把函數調用送入線程池中
有一些配置和event loop的類型你可使用, 可是若是你想去執行一個函數, 可使用下面的配置, 並且在大多數場景中這樣就夠了:
import asyncio async def speak_async(): print('OMG asynchronicity!') loop = asyncio.get_event_loop() loop.run_until_complete(speak_async()) loop.close()
最後三行是重點。 asyncio啓動默認的event loop(asyncio.get_event_loop()), 調度並執行異步任務, 關閉event loop。
loop.run_until_complete()這個函數是阻塞執行的, 直到全部的異步函數執行完畢。 由於咱們的程序是單線程運行的, 因此, 它沒辦法調度到別的線程執行。
你可能會認爲這不是頗有用, 由於咱們的程序阻塞在event loop上(就像IO調用), 可是想象一下這樣: 咱們能夠把咱們的邏輯封裝在異步函數中, 這樣你就能同時執行不少的異步請求了, 好比在一個web服務器中。
你能夠把event loop放在一個單獨的線程中, 讓它執行IO密集型的請求, 而主線程能夠繼續處理程序邏輯或者UI渲染。
OK, 讓我看一個稍微長一點的例子, 這個例子是能夠實際運行的。 例子是一個簡單的從Reddit的/r/python, /r/programming, and /r/compsci頁面異步獲取JSON數據, 解析, 打印出這些頁面發表的文章。
get_json()方法是被get_reddit_top()調用的, get_reddit_top()發起了一個HTTP GET請求到Reddit。 當調用被await修飾, event loop就會繼續在等待請求返回的時候處理其餘的協程。 一旦請求返回, 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()讓event loop處理多個協程, 而後讓event loop一直執行, 直處處理了全部的請求。
爲了執行這個程序, 須要安裝aiohttp, 你能夠用pip來安裝:
pip install aiohttp
要保證這個程序運行在python3.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分鐘去學習異步相關的知識, 你就能更好的把它應用在你的項目中。