[轉載]Python 3.5 協程到底是個啥

http://blog.rainy.im/2016/03/10/how-the-heck-does-async-await-work-in-python-3-5/html

[譯] Python 3.5 協程到底是個啥

Yusheng · Mar 10, 2016前端

做者是 Python 語言的核心開發人員,這篇文章也是我分享的,可是在翻譯以前並無看得太仔細。做者在這篇文章裏先是是從 Python 異步編程的發展歷史一直介紹到 Python 3.5 中 async/await 新特性的提出,又從底層的實現的差別一直延伸到完整的代碼實例,來講明舊的生成器做爲協程的「權宜之計」與新語法的差異。真正作到了深刻淺出地釐清了 Python 3.5 異步編程的各類概念及其原理,很是值得學習!協程和事件循環不管從理念仍是實現上,都比線程好太多,看完這篇文章以後你必定會和做者有一樣的感覺:「我要在全部地方都用協程!」,因此,還在用 2.7 的,趕忙升級吧!python

python-octocat

如下爲譯文全文,感謝@L9m@iThreeKing的審校。git


做爲 Python 核心開發者之一,讓我很想了解這門語言是如何運做的。我發現總有一些陰暗的角落我對其中錯綜複雜的細節不是很清楚,可是爲了可以有助於 Python 的一些問題和其總體設計,我以爲我應該試着去理解 Python 的核心語法和內部運做機制。github

可是直到最近我才理解 Python 3.5 中 async/await 的原理。我知道 Python 3.3 中的 yield from 和 Python 3.4 中的 asyncio 組合得來這一新語法。但較少處理網絡相關的問題 - asyncio 並不只限於此但確是重要用途 - 使我沒太注意 async/await 。我知道:golang

yield from iterator 

(本質上)至關於:編程

for x in iterator: yield x 

我知道 asyncio 是事件循環框架能夠進行異步編程,可是我只是知道這裏面每一個單詞的意思而已,從沒深刻研究 async/await 語法組合背後的原理,我發現不理解 Python 中的異步編程已經對我形成了困擾。所以我決定花時間弄清楚這背後的原理到底是什麼。我從不少人那裏得知他們也不瞭解異步編程的原理,所以我決定寫這篇論文(是的,因爲這篇文章花費時間之久以及篇幅之長,個人妻子已經將其定義爲一篇論文)。api

因爲我想要正確地理解這些語法的原理,這篇文章涉及到一些關於 CPython 較爲底層的技術細節。若是這些細節超出了你想了解的內容,或者你不能徹底理解它們,都不要緊,由於我爲了不這篇文章演變成一本書那麼長,省略了一些 CPython 內部的細枝末節(好比說,若是你不知道 code object 有 flags,甚至不知道什麼是 code object,這都不要緊,也不用必定要從這篇文字中得到什麼)。我試着在最後一小節中用更直接的方法作了總結,若是以爲文章對你來講細節太多,你徹底能夠跳過。瀏覽器

關於 Python 協程的歷史課

根據維基百科給出的定義,「協程 是爲非搶佔式多任務產生子程序的計算機程序組件,協程容許不一樣入口點在不一樣位置暫停或開始執行程序」。從技術的角度來講,「協程就是你能夠暫停執行的函數」。若是你把它理解成「就像生成器同樣」,那麼你就想對了。安全

退回到 Python 2.2,生成器第一次在PEP 255中提出(那時也把它成爲迭代器,由於它實現了迭代器協議)。主要是受到Icon編程語言的啓發,生成器容許建立一個在計算下一個值時不會浪費內存空間的迭代器。例如你想要本身實現一個 range() 函數,你能夠用當即計算的方式建立一個整數列表:

def eager_range(up_to): """Create a list of integers, from 0 to up_to, exclusive.""" sequence = [] index = 0 while index < up_to: sequence.append(index) index += 1 return sequence 

然而這裏存在的問題是,若是你想建立從0到1,000,000這樣一個很大的序列,你不得不建立能容納1,000,000個整數的列表。可是當加入了生成器以後,你能夠不用建立完整的序列,你只須要可以每次保存一個整數的內存便可。

def lazy_range(up_to): """Generator to return the sequence of integers from 0 to up_to, exclusive.""" index = 0 while index < up_to: yield index index += 1 

讓函數遇到 yield 表達式時暫停執行 - 雖然直到 Python 2.5 才成爲聲明語句 - 而且可以在後面從新執行,這對於減小內存使用、生成無限序列很是有用。

你有可能已經發現,生成器徹底就是關於迭代器的。有一種更好的方式生成迭代器固然很好(尤爲是當你能夠給一個生成器對象添加 __iter__() 方法時),可是人們知道,若是能夠利用生成器「暫停」的部分,添加「將東西發送回生成器」的功能,那麼 Python 忽然就有了協程的概念(固然這裏的協程僅限於 Python 中的概念;Python 中真實的協程在後面纔會討論)。將東西發送回暫停了的生成器這一特性經過 PEP 342添加到了 Python 2.5。與其它特性一塊兒,PEP 342 爲生成器引入了 send() 方法。這讓咱們不只能夠暫停生成器,並且可以傳遞值到生成器暫停的地方。仍是以咱們的 range() 爲例,你可讓序列向前或向後跳過幾個值:

def jumping_range(up_to): """Generator for the sequence of integers from 0 to up_to, exclusive. Sending a value into the generator will shift the sequence by that amount. """ index = 0 while index < up_to: jump = yield index if jump is None: jump = 1 index += jump if __name__ == '__main__': iterator = jumping_range(5) print(next(iterator)) # 0 print(iterator.send(2)) # 2 print(next(iterator)) # 3 print(iterator.send(-1)) # 2 for x in iterator: print(x) # 3, 4 

直到PEP 380 爲 Python 3.3 添加了 yield from以前,生成器都沒有變更。嚴格來講,這一特性讓你可以從迭代器(生成器恰好也是迭代器)中返回任何值,從而能夠乾淨利索的方式重構生成器。

def lazy_range(up_to): """Generator to return the sequence of integers from 0 to up_to, exclusive.""" index = 0 def gratuitous_refactor(): while index < up_to: yield index index += 1 yield from gratuitous_refactor() 

yield from 經過讓重構變得簡單,也讓你可以將生成器串聯起來,使返回值能夠在調用棧中上下浮動,而不需對編碼進行過多改動。

def bottom(): # Returning the yield lets the value that goes up the call stack to come right back # down. return (yield 42) def middle(): return (yield from bottom()) def top(): return (yield from middle()) # Get the generator. gen = top() value = next(gen) print(value) # Prints '42'. try: value = gen.send(value * 2) except StopIteration as exc: value = exc.value print(value) # Prints '84'. 

總結

Python 2.2 中的生成器讓代碼執行過程能夠暫停。Python 2.5 中能夠將值返回給暫停的生成器,這使得 Python 中協程的概念成爲可能。加上 Python 3.3 中的 yield from,使得重構生成器與將它們串聯起來都很簡單。

什麼是事件循環?

若是你想了解 async/await,那麼理解什麼是事件循環以及它是如何讓異步編程變爲可能就至關重要了。若是你曾作過 GUI 編程 - 包括網頁前端工做 - 那麼你已經和事件循環打過交道。可是因爲異步編程的概念做爲 Python 語言結構的一部分仍是最近纔有的事,你恰好不知道什麼是事件循環也很正常。

回到維基百科,事件循環 「是一種等待程序分配事件或消息的編程架構」。基本上來講事件循環就是,「當A發生時,執行B」。或許最簡單的例子來解釋這一律念就是用每一個瀏覽器中都存在的JavaScript事件循環。當你點擊了某個東西(「當A發生時」),這一點擊動做會發送給JavaScript的事件循環,並檢查是否存在註冊過的 onclick 回調來處理這一點擊(「執行B」)。只要有註冊過的回調函數就會伴隨點擊動做的細節信息被執行。事件循環被認爲是一種循環是由於它不停地收集事件並經過循環來發如何應對這些事件。

對 Python 來講,用來提供事件循環的 asyncio 被加入標準庫中。asyncio 重點解決網絡服務中的問題,事件循環在這裏未來自套接字(socket)的 I/O 已經準備好讀和/或寫做爲「當A發生時」(經過selectors模塊)。除了 GUI 和 I/O,事件循環也常常用於在別的線程或子進程中執行代碼,並將事件循環做爲調節機制(例如,合做式多任務)。若是你剛好理解 Python 的 GIL,事件循環對於須要釋放 GIL 的地方頗有用。

總結

事件循環提供一種循環機制,讓你能夠「在A發生時,執行B」。基本上來講事件循環就是監聽當有什麼發生時,同時事件循環也關心這件事並執行相應的代碼。Python 3.4 之後經過標準庫 asyncio 得到了事件循環的特性。

async 和 await 是如何運做的

Python 3.4 中的方式

在 Python 3.3 中出現的生成器與以後以 asyncio 的形式出現的事件循環之間,Python 3.4 經過併發編程的形式已經對異步編程有了足夠的支持。異步編程簡單來講就是代碼執行的順序在程序運行前是未知的(所以才稱爲異步而非同步)。併發編程是代碼的執行不依賴於其餘部分,即使是全都在同一個線程內執行(併發不是並行)。例如,下面 Python 3.4 的代碼分別以異步和併發的函數調用實現按秒倒計時。

import asyncio # Borrowed from http://curio.readthedocs.org/en/latest/tutorial.html. @asyncio.coroutine def countdown(number, n): while n > 0: print('T-minus', n, '({})'.format(number)) yield from asyncio.sleep(1) n -= 1 loop = asyncio.get_event_loop() tasks = [ asyncio.ensure_future(countdown("A", 2)), asyncio.ensure_future(countdown("B", 3))] loop.run_until_complete(asyncio.wait(tasks)) loop.close() 

Python 3.4 中,asyncio.coroutine 修飾器用來標記做爲協程的函數,這裏的協程是和asyncio及其事件循環一塊兒使用的。這賦予了 Python 第一個對於協程的明肯定義:實現了PEP 342添加到生成器中的這一方法的對象,並經過[collections.abc.Coroutine這一抽象基類]表徵的對象。這意味着忽然之間全部實現了協程接口的生成器,即使它們並非要以協程方式應用,都符合這必定義。爲了修正這一點,asyncio 要求全部要用做協程的生成器必須asyncio.coroutine修飾

有了對協程明確的定義(可以匹配生成器所提供的API),你能夠對任何asyncio.Future對象使用 yield from,從而將其傳遞給事件循環,暫停協程的執行來等待某些事情的發生( future 對象並不重要,只是asyncio細節的實現)。一旦 future 對象獲取了事件循環,它會一直在那裏監聽,直到完成它須要作的一切。當 future 完成本身的任務以後,事件循環會察覺到,暫停並等待在那裏的協程會經過send()方法獲取future對象的返回值並開始繼續執行。

以上面的代碼爲例。事件循環啓動每個 countdown() 協程,一直執行到碰見其中一個協程的 yield from 和 asyncio.sleep() 。這樣會返回一個 asyncio.Future對象並將其傳遞給事件循環,同時暫停這一協程的執行。事件循環會監控這一future對象,直到倒計時1秒鐘以後(同時也會檢查其它正在監控的對象,好比像其它協程)。1秒鐘的時間一到,事件循環會選擇剛剛傳遞了future對象並暫停了的 countdown() 協程,將future對象的結果返回給協程,而後協程能夠繼續執行。這一過程會一直持續到全部的 countdown() 協程執行完畢,事件循環也被清空。稍後我會給你展現一個完整的例子,用來講明協程/事件循環之類的這些東西到底是如何運做的,可是首先我想要解釋一下asyncawait

Python 3.5 從 yield from 到 await

在 Python 3.4 中,用於異步編程並被標記爲協程的函數看起來是這樣的:

# This also works in Python 3.5. @asyncio.coroutine def py34_coro(): yield from stuff() 

Python 3.5 添加了types.coroutine 修飾器,也能夠像 asyncio.coroutine 同樣將生成器標記爲協程。你能夠用 async def 來定義一個協程函數,雖然這個函數不能包含任何形式的 yield 語句;只有 return 和 await 能夠從協程中返回值。

async def py35_coro(): await stuff() 

雖然 async 和 types.coroutine 的關鍵做用在於鞏固了協程的定義,可是它將協程從一個簡單的接口變成了一個實際的類型,也使得一個普通生成器和用做協程的生成器之間的差異變得更加明確(inspect.iscoroutine() 函數 甚至明確規定必須使用 async 的方式定義才行)。

你將發現不只僅是 async,Python 3.5 還引入 await 表達式(只能用於async def中)。雖然await的使用和yield from很像,但await能夠接受的對象倒是不一樣的。await 固然能夠接受協程,由於協程的概念是全部這一切的基礎。可是當你使用 await 時,其接受的對象必須是awaitable 對象:必須是定義了__await__()方法且這一方法必須返回一個不是協程的迭代器。協程自己也被認爲是 awaitable 對象(這也是collections.abc.Coroutine繼承 collections.abc.Awaitable的緣由)。這必定義遵循 Python 將大部分語法結構在底層轉化成方法調用的傳統,就像 a + b 其實是a.__add__(b) 或者 b.__radd__(a)

yield from 和 await 在底層的差異是什麼(也就是types.coroutineasync def的差異)?讓咱們看一下上面兩則Python 3.5代碼的例子所產生的字節碼在本質上有何差別。py34_coro()的字節碼是:

>>> dis.dis(py34_coro) 2 0 LOAD_GLOBAL 0 (stuff) 3 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 6 GET_YIELD_FROM_ITER 7 LOAD_CONST 0 (None) 10 YIELD_FROM 11 POP_TOP 12 LOAD_CONST 0 (None) 15 RETURN_VALUE 

py35_coro()的字節碼是:

>>> dis.dis(py35_coro) 1 0 LOAD_GLOBAL 0 (stuff) 3 CALL_FUNCTION 0 (0 positional, 0 keyword pair) 6 GET_AWAITABLE 7 LOAD_CONST 0 (None) 10 YIELD_FROM 11 POP_TOP 12 LOAD_CONST 0 (None) 15 RETURN_VALUE 

忽略因爲py34_coro()asyncio.coroutine 修飾器所帶來的行號的差異,二者之間惟一可見的差別是GET_YIELD_FROM_ITER操做碼 對比GET_AWAITABLE操做碼。兩個函數都被標記爲協程,所以在這裏沒有差異。GET_YIELD_FROM_ITER 只是檢查參數是生成器仍是協程,不然將對其參數調用iter()方法(只有用在協程內部的時候yield from所對應的操做碼才能夠接受協程對象,在這個例子裏要感謝types.coroutine修飾符將這個生成器在C語言層面標記爲CO_ITERABLE_COROUTINE)。

可是 GET_AWAITABLE的作法不一樣,其字節碼像GET_YIELD_FROM_ITER同樣接受協程,可是不接受沒有被標記爲協程的生成器。就像前面討論過的同樣,除了協程之外,這一字節碼還能夠接受awaitable對象。這使得yield fromawait表達式都接受協程但分別接受通常的生成器和awaitable對象。

你可能會想,爲何基於async的協程和基於生成器的協程會在對應的暫停表達式上面有所不一樣?主要緣由是出於最優化Python性能的考慮,確保你不會將恰好有一樣API的不一樣對象混爲一談。因爲生成器默認實現協程的API,所以頗有可能在你但願用協程的時候錯用了一個生成器。而因爲並非全部的生成器均可以用在基於協程的控制流中,你須要避免錯誤地使用生成器。可是因爲 Python 並非靜態編譯的,它最好也只能在用基於生成器定義的協程時提供運行時檢查。這意味着當用types.coroutine時,Python 的編譯器將沒法判斷這個生成器是用做協程仍是僅僅是普通的生成器(記住,僅僅由於types.coroutine這一語法的字面意思,並不意味着在此以前沒有人作過types = spam的操做),所以編譯器只能基於當前的狀況生成有着不一樣限制的操做碼。

關於基於生成器的協程和async定義的協程之間的差別,我想說明的關鍵點是隻有基於生成器的協程能夠真正的暫停執行並強制性返回給事件循環。你可能不瞭解這些重要的細節,由於一般你調用的像是asyncio.sleep() function 這種事件循環相關的函數,因爲事件循環實現他們本身的API,而這些函數會處理這些小的細節。對於咱們絕大多數人來講,咱們只會跟事件循環打交道,而不須要處理這些細節,所以能夠只用async定義的協程。可是若是你和我同樣好奇爲何不能在async定義的協程中使用asyncio.sleep(),那麼這裏的解釋應該可讓你頓悟。

總結

讓咱們用簡單的話來總結一下。用async def能夠定義獲得協程。定義協程的另外一種方式是經過types.coroutine修飾器 -- 從技術實現的角度來講就是添加了 CO_ITERABLE_COROUTINE標記 -- 或者是collections.abc.Coroutine的子類。你只能經過基於生成器的定義來實現協程的暫停。

awaitable 對象要麼是一個協程要麼是一個定義了__await__()方法的對象 -- 也就是collections.abc.Awaitable -- 且__await__()必須返回一個不是協程的迭代器。await表達式基本上與yield from相同但只能接受awaitable對象(普通迭代器不行)。async定義的函數要麼包含return語句 -- 包括全部Python函數缺省的return None -- 和/或者 await表達式(yield表達式不行)。async函數的限制確保你不會將基於生成器的協程與普通的生成器混合使用,由於對這兩種生成器的指望是很是不一樣的。

將 async/await 看作異步編程的 API

我想要重點指出的地方實際上在我看David Beazley's Python Brasil 2015 keynote以前尚未深刻思考過。在他的演講中,David 指出 async/await 其實是異步編程的 API (他在 Twitter 上向我重申過)。David 的意思是人們不該該將async/await等同於asyncio,而應該將asyncio看做是一個利用async/await API 進行異步編程的框架。

David 將 async/await 看做是異步編程的API建立了 curio 項目來實現他本身的事件循環。這幫助我弄清楚 async/await 是 Python 建立異步編程的原料,同時又不會將你束縛在特定的事件循環中也無需與底層的細節打交道(不像其餘編程語言將事件循環直接整合到語言中)。這容許像 curio 同樣的項目不只能夠在較低層面上擁有不一樣的操做方式(例如 asyncio 利用 future 對象做爲與事件循環交流的 API,而 curio 用的是元組),同時也能夠集中解決不一樣的問題,實現不一樣的性能特性(例如 asyncio 擁有一整套框架來實現運輸層和協議層,從而使其變得可擴展,而 curio 只是簡單地讓用戶來考慮這些但同時也讓它運行地更快)。

考慮到 Python 異步編程的(短暫)歷史,能夠理解人們會誤認爲 async/await == asyncio。我是說asyncio幫助咱們能夠在 Python 3.4 中實現異步編程,同時也是 Python 3.5 中引入async/await的推進因素。可是async/await 的設計意圖就是爲了讓其足夠靈活從而不須要依賴asyncio或者僅僅是爲了適應這一框架而扭曲關鍵的設計決策。換句話說,async/await 延續了 Python 設計儘量靈活的傳統同時又很是易於使用(實現)。

一個例子

到這裏你的大腦可能已經灌滿了新的術語和概念,致使你想要從總體上把握全部這些東西是如何讓你能夠實現異步編程的稍微有些困難。爲了幫助你讓這一切更加具體化,這裏有一個完整的(僞造的)異步編程的例子,將代碼與事件循環及其相關的函數一一對應起來。這個例子裏包含的幾個協程,表明着火箭發射的倒計時,而且看起來是同時開始的。這是經過併發實現的異步編程;3個不一樣的協程將分別獨立運行,而且都在同一個線程內完成。

import datetime import heapq import types import time class Task: """Represent how long a coroutine should before starting again. Comparison operators are implemented for use by heapq. Two-item tuples unfortunately don't work because when the datetime.datetime instances are equal, comparison falls to the coroutine and they don't implement comparison methods, triggering an exception. Think of this as being like asyncio.Task/curio.Task. """ def __init__(self, wait_until, coro): self.coro = coro self.waiting_until = wait_until def __eq__(self, other): return self.waiting_until == other.waiting_until def __lt__(self, other): return self.waiting_until < other.waiting_until class SleepingLoop: """An event loop focused on delaying execution of coroutines. Think of this as being like asyncio.BaseEventLoop/curio.Kernel. """ def __init__(self, *coros): self._new = coros self._waiting = [] def run_until_complete(self): # Start all the coroutines. for coro in self._new: wait_for = coro.send(None) heapq.heappush(self._waiting, Task(wait_for, coro)) # Keep running until there is no more work to do. while self._waiting: now = datetime.datetime.now() # Get the coroutine with the soonest resumption time. task = heapq.heappop(self._waiting) if now < task.waiting_until: # We're ahead of schedule; wait until it's time to resume. delta = task.waiting_until - now time.sleep(delta.total_seconds()) now = datetime.datetime.now() try: # It's time to resume the coroutine. wait_until = task.coro.send(now) heapq.heappush(self._waiting, Task(wait_until, task.coro)) except StopIteration: # The coroutine is done. pass @types.coroutine def sleep(seconds): """Pause a coroutine for the specified number of seconds. Think of this as being like asyncio.sleep()/curio.sleep(). """ now = datetime.datetime.now() wait_until = now + datetime.timedelta(seconds=seconds) # Make all coroutines on the call stack pause; the need to use `yield` # necessitates this be generator-based and not an async-based coroutine. actual = yield wait_until # Resume the execution stack, sending back how long we actually waited. return actual - now async def countdown(label, length, *, delay=0): """Countdown a launch for `length` seconds, waiting `delay` seconds. This is what a user would typically write. """ print(label, 'waiting', delay, 'seconds before starting countdown') delta = await sleep(delay) print(label, 'starting after waiting', delta) while length: print(label, 'T-minus', length) waited = await sleep(1) length -= 1 print(label, 'lift-off!') def main(): """Start the event loop, counting down 3 separate launches. This is what a user would typically write. """ loop = SleepingLoop(countdown('A', 5), countdown('B', 3, delay=2), countdown('C', 4, delay=1)) start = datetime.datetime.now() loop.run_until_complete() print('Total elapsed time is', datetime.datetime.now() - start) if __name__ == '__main__': main() 

就像我說的,這是僞造出來的,可是若是你用 Python 3.5 去運行,你會發現這三個協程在同一個線程內獨立運行,而且總的運行時間大約是5秒鐘。你能夠將TaskSleepingLoopsleep()看做是事件循環的提供者,就像asynciocurio所提供給你的同樣。對於通常的用戶來講,只有countdown()main()函數中的代碼纔是重要的。正如你所見,asyncawait或者是這整個異步編程的過程並沒什麼黑科技;只不過是 Python 提供給你幫助你更簡單地實現這類事情的API。

我對將來的但願和夢想

如今我理解了 Python 中的異步編程是如何運做的了,我想要一直用它!這是如此絕妙的概念,比你以前用過的線程好太多了。可是問題在於 Python 3.5 還太新了,async/await也太新了。這意味着尚未太多庫支持這樣的異步編程。例如,爲了實現 HTTP 請求你要麼不得不本身徒手構建 ,要麼用像是 aiohttp 之類的框架 將 HTTP 添加在另一個事件循環的頂端,或者寄但願於更多像hyper 庫同樣的項目不停涌現,能夠提供對於 HTTP 之類的抽象,可讓你隨便用任何 I/O 庫 來實現你的需求(雖然惋惜的是 hyper目前只支持 HTTP/2)。

對於我我的來講,我但願更多像hyper同樣的項目能夠脫穎而出,這樣咱們就能夠在從 I/O中讀取與解讀二進制數據之間作出明確區分。這樣的抽象很是重要,由於Python多數 I/O 庫中處理 I/O 和處理數據是牢牢耦合在一塊兒的。Python 的標準庫 http就有這樣的問題,它不提供 HTTP解析而只有一個鏈接對象爲你處理全部的 I/O。而若是你寄但願於requests能夠支持異步編程,那你的但願已經破滅了,由於 requests 的同步 I/O 已經烙進它的設計中了。Python 在網絡堆棧上不少層都缺乏抽象定義,異步編程能力的改進使得 Python 社區有機會對此做出修復。咱們能夠很方便地讓異步代碼像同步同樣執行,這樣一些填補異步編程空白的工具能夠安全地運行在兩種環境中。

我但願 Python 可讓 async 協程支持 yield。或者須要用一個新的關鍵詞來實現(可能像 anticipate之類?),由於不能僅靠async就實現事件循環讓我很困擾。幸運的是,我不是惟一一個這麼想的人,並且PEP 492的做者也和我意見一致,我以爲仍是有機會能夠移除掉這點小瑕疵。

結論

基本上 async 和 await 產生神奇的生成器,咱們稱之爲協程,同時須要一些額外的支持例如 awaitable 對象以及將普通生成器轉化爲協程。全部這些加到一塊兒來支持併發,這樣才使得 Python 更好地支持異步編程。相比相似功能的線程,這是一個更妙也更簡單的方法。我寫了一個完整的異步編程例子,算上註釋只用了不到100行 Python 代碼 -- 但仍然很是靈活與快速(curio FAQ 指出它比 twisted 要快 30-40%,可是要比 gevent 慢 10-15%,並且所有都是有純粹的 Python 實現的;記住Python 2 + Twisted 內存消耗更少同時比Go更容易調試,想象一下這些能幫你實現什麼吧!)。我很是高興這些可以在 Python 3 中成爲現實,我也很是期待 Python 社區能夠接納並將其推廣到各類庫和框架中區,可使咱們都可以受益於 Python 異步編程帶來的好處!

 Python譯文

- END -

相關文章
相關標籤/搜索