Python async/await Tutorial(翻譯)

原文連接:
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

Coroutines

在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方法。 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/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方式。

Running the event loop

上面描述的協程例子都不會正常的運行, 若是要運行, 須要用到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渲染。

An example

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分鐘去學習異步相關的知識, 你就能更好的把它應用在你的項目中。

相關文章
相關標籤/搜索