由於工做中慢慢開始用python的協程,因此想更好的理解一下實現方式,故翻譯此文python
原文中把詞彙表放到最後,可是我我的以爲放在最開始比較好,這樣能夠增長當你看原文時的理解程度數據庫
原生協程函數 Native coroutine function:express
由async def定義的協程函數,可使用await和return value語句編程
原生協程 Native coroutine:session
原生協程函數返回的對象。見「await表達式」一節。併發
基於生成器的協程函數 Generator-based coroutine function:app
基於生成器語法的協程,最多見的是用 @asyncio.coroutine裝飾過的函數。框架
基於生成器的協程 Generator-based coroutine:異步
基於生成器的協程函數返回的對象。async
協程 Coroutine:
「原生協程」和「基於生成器的協程」都是協程。
協程對象 Coroutine object:
「原生協程對象」和「基於生成器的協程對象」都是協程對象。
Future-like對象 Future-like object:
一個有__await__方法的對象,或一個有tp_as_async->am_await函數的C語言對象,它們返回一個迭代器。Future-like對象能夠在協程裏被一條await語句消費(consume)。協程會被await語句掛起,直到await語句右邊的Future-like對象的__await__執行完畢、返回結果。見「await表達式」一節。
Awaitable
一個Future-like對象或一個協程對象。見「await表達式」一節。
異步上下文管理器 Asynchronous context manager:
有__aenter__和__aexit__方法的對象,能夠被async with語句使用。見「異步上下文管理器和‘async with’」一節。
可異步迭代對象 Asynchronous iterable:
有__aiter__方法的對象, 該方法返回一個異步迭代器對象。能夠被async for語句使用。見「異步迭代器和‘async for’」一節。
異步迭代器 Asynchronous iterator:
有__anext__方法的對象。見「異步迭代器和‘async for’」一節。
隨着互聯網和鏈接程序的增加,引起了對響應性和可擴展代碼的需求,該提議的目標是讓咱們共容易的經過編寫顯示異步,高併發的python代碼而且更加Pythonic
它提出把寫成的概念獨立出來,並引入新的支持語法。最終的目標是幫助在python中創建一個通用的,易於接近的異步編程構思模型,並使其儘量接近於同步編程(說白了就是讓你經過相似寫同步編程的方式,寫出異步代碼)
這個PEPE建設異步任務是相似於標準模塊asyncio.events.AbstractEventLoop的事件循環調度和協調。雖然這個PEP不依賴人去特定的時間循環實現,但它僅僅與使用yield做爲調度程序信號的協程類型相關,表示協程將等待知道事件(例如:IO)完成
咱們相信,這裏提出的更改將有助於python在快速增加的異步編程領域保持更好的競爭力,由於許多其餘語言已經採或將要採用相似的功能
對Python 3.5的初始beta版本的反饋致使從新設計支持此PEP的對象模型,以更清楚地將原生協程與生成器分離 - 而不是一種新的生成器,如今原生協程有明確的獨立類型
這個改變主要是爲了解決原生協程在tornado裏使用出現的一些問題
在CPython3.5.2 中更新了__aiter__ 協議。
在3.5.2以前,__aiter__ 是被指望返回一個等待解析爲異步迭代器,從3.5.2開始,__aiter__ 應該直接返回異步迭代器
若是在3.5.2中使用舊協議中,Python將引起PendingDeprecationWarning異常
在CPython 3.6中,舊的__aiter__協議仍將受到引起DeprecationWarning的支持
在CPython 3.7中,將再也不支持舊的__aiter__協議:若是__aiter__返回除異步迭代器以外的任何內容,則將引起RuntimeError。
當前的Python支持經過生成器(PEP342)實現協程,並經過PEP380中引入的yield from 語法進一步加強,這種方法有不少缺點:
這個PEP把協程從生成器獨立出來,成爲Python的一個原生事物。這會消除協程和生成器之間的混淆,方便編寫不依賴特定庫的協程代碼。也爲linter和IDE進行代碼靜態分析提供了機會。
使用原生協程和相應的新語法,咱們能夠在異步編程時使用上下文管理器(context manager)和迭代器。以下文所示,新的async with語句能夠在進入、離開運行上下文(runtime context)時進行異步調用,而async for語句能夠在迭代時進行異步調用。
該提議引入了新的語法和語義來加強Python對協程支持。
請理解Python現有的協程(見PEP 342和PEP 380),此次改變的動機來自於asyncio框架(PEP 3156)和Confunctions提案(PEP 3152,此PEP已經被廢棄)。
由此,在本文中,咱們使用「原生協程」指用新語法聲明的協程。「生成器實現的協程」指用傳統方法實現的協程。「協程」則用在兩個均可以使用的地方。
使用如下語法聲明原生協程:
async def read_data(db): pass
協程語法的關鍵點:
types模塊添加了一個新函數coroutine(fn),使用它,「生成器實現的協程」和「原生協程」之間能夠進行互操做。
@types.coroutine def process_data(db): data = yield from read_data(db) ...
該函數將CO_ITERABLE_COROUTINE標誌應用於生成器函數的代碼對象,使其返回一個協程對象。若是fn不是生成器函數,它將被包裝。若是它返回一個生成器,它將被包裝在一個等待的代理對象中(參見下面的等待對象的定義)。
types.coroutine()不會設置CO_COROUTINE標識,只有用新語法定義的原生協程纔會有這個標識。
新的await表達式用於得到協程執行的結果:
async def read_data(db): data = await db.fetch('SELECT ...') ...
await 和yield from 是很是相似的,會掛起read_data的執行,直到等待db.fetch完成並返回結果數據。
await使用yield from的實現,可是加入了一個額外步驟——驗證它的參數類型。await只接受awaitable對象,awaitable對象是如下的其中一個:
例如,在asyncio模塊,要想在await語句裏使用Future對象,惟一的修改是給asyncio.Future加一行:__await__ = __iter__
在本文中,有__await__方法的對象被稱爲Future-like對象(協程會被await語句掛起,直到await語句右邊的Future-like對象的__await__執行完畢、返回結果。)
若是__await__返回的不是一個迭代器,則引起TypeError異常。
在CPython C API,有tp_as_async.am_await函數的對象,該函數返回一個迭代器(相似__await__方法)
若是在async def函數以外使用await語句,會引起SyntaxError異常。這和在def函數以外使用yield語句同樣。
若是await右邊不是一個awaitable對象,會引起TypeError異常。
有效的語法示例
Expression | Will be parsed as |
---|---|
if await fut: pass | if (await fut): pass |
if await fut + 1: pass | if (await fut) + 1: pass |
pair = await fut, 'spam' | pair = (await fut), 'spam' |
with await fut, open(): pass | with (await fut), open(): pass |
await foo()['spam'].baz()() | await ( foo()['spam'].baz()() ) |
return await coro() | return ( await coro() ) |
res = await coro() ** 2 | res = (await coro()) ** 2 |
func(a1=await coro(), a2=0) | func(a1=(await coro()), a2=0) |
await foo() + await bar() | (await foo()) + (await bar()) |
-await foo() | -(await foo()) |
無效的用法
Expression | Should be written as |
---|---|
await await coro() | await (await coro()) |
await -coro() | await (-coro()) |
異步上下文管理器(asynchronous context manager),能夠在它的enter和exit方法裏掛起、調用異步代碼。
爲此,咱們設計了一套方案,添加了兩個新的魔術方法:__aenter__和__aexit__,它們必須返回一個awaitable。
異步上下文管理器的一個示例:
class AsyncContextManager: async def __aenter__(self): await log('entering context') async def __aexit__(self, exc_type, exc, tb): await log('exiting context')
採納了一個異步上下文管理器的新語法
async with EXPR as VAR:
BLOCK
這在語義上等同於:
mgr = (EXPR) aexit = type(mgr).__aexit__ aenter = type(mgr).__aenter__(mgr) VAR = await aenter try: BLOCK except: if not await aexit(mgr, *sys.exc_info()): raise else: await aexit(mgr, None, None, None)
與常規with語句同樣,能夠在單個async with語句中指定多個上下文管理器。
在使用async with時,若是上下文管理器沒有__aenter__和__aexit__方法,則會引起錯誤。在async def函數以外使用async with則會引起SyntaxError異常。
使用異步上下文管理器,能夠輕鬆地爲協同程序實現適當的數據庫事務管理器:
async def commit(session, data): ... async with session.transaction(): ... await session.update(data) ...
加鎖的處理也更加簡潔
async with lock:
...
而再也不是:
with (yield from lock): ...
異步迭代器能夠在它的iter實現裏掛起、調用異步代碼,也能夠在它的__next__方法裏掛起、調用異步代碼。要支持異步迭代,須要:
一個一步迭代的例子:
class AsyncIterable: def __aiter__(self): return self async def __anext__(self): data = await self.fetch_data() if data: return data else: raise StopAsyncIteration async def fetch_data(self): ...
採納了一個迭代異步迭代器的新語法:
async for TARGET in ITER: BLOCK else: BLOCK2
在語義上等同於:
iter = (ITER) iter = type(iter).__aiter__(iter) running = True while running: try: TARGET = await type(iter).__anext__(iter) except StopAsyncIteration: running = False else: BLOCK else: BLOCK2
若是async for的迭代器不支持__aiter__方法,則引起TypeError異常。若是在async def函數外使用async for,則引起SyntaxError異常。
和普通的for語句同樣,async for有一個可選的else分句。
使用異步迭代協議,能夠在迭代期間異步緩衝數據:
async for data in cursor: ...
其中cursor是一個異步迭代器,它在每N次迭代後從數據庫中預取N行數據。
如下代碼說明了新的異步迭代協議:
class Cursor: def __init__(self): self.buffer = collections.deque() async def _prefetch(self): ... def __aiter__(self): return self async def __anext__(self): if not self.buffer: self.buffer = await self._prefetch() if not self.buffer: raise StopAsyncIteration return self.buffer.popleft()
而後,能夠這樣使用Cursor類
async for row in Cursor(): print(row)
與下述代碼相同:
i = await Cursor().__aiter__() while True: try: row = await i.__anext__() except StopAsyncIteration: break else: print(row)
如下是將常規迭代轉換爲異步迭代的實用程序類。雖然這不是一件很是有用的事情,但代碼說明了常規迭代器和異步迭代器之間的關係。
class AsyncIteratorWrapper: def __init__(self, obj): self._it = iter(obj) def __aiter__(self): return self async def __anext__(self): try: value = next(self._it) except StopIteration: raise StopAsyncIteration return value async for letter in AsyncIteratorWrapper("abc"): print(letter)
協程在內部仍然是基於生成器實現的,所以,在PEP479以前,下面二者是沒有區別的
def g1(): yield from fut return 'spam'
和
def g2(): yield from fut raise StopIteration('spam')
因爲PEP 479已被正式採納,並做用於協程,如下代碼的StopIteration會被包裝(wrapp)成一個RuntimeError。
async def a1(): await fut raise StopIteration('spam')
因此,要想通知外部代碼迭代已經結束,拋出一個StopIteration異常的方法不行了。所以,添加了一個新的內置異常StopAsyncIteration,用於表示迭代結束。
此外,根據PEP 479,協程拋出的全部StopIteration異常都會被包裝成RuntimeError異常。
本節僅適用於具備CO_COROUTINE的原生協程,即便用新的async def 定義的函數
對於asyncio模塊裏現有的「基於生成器的協程」,仍然保持不變。
爲了把協程和生成器的概念區分開來:
協程是基於生成器實現的,所以它們有共同的代碼。像生成器對象那樣,協程也有throw(),send()和close()方法。
對於協程,StopIteration和GeneratorExit起着一樣的做用(雖然PEP 479已經應用於協程)。詳見PEP 34二、PEP 380,以及Python文檔。
對於協程,send(),throw()方法用於往Future-like對象發送內容、拋出異常。
初級開發者在使用協程時可能忘記使用yield from語句,好比:
@asyncio.coroutine def useful(): asyncio.sleep(1) # this will do nothing without 'yield from'
爲了調試這種錯誤,在asyncio中有一個特殊的調試模式,其中@coroutine裝飾器用一個特殊對象包裝全部函數,並使用析構函數記錄警告。每當一個包裝的生成器被垃圾回收時,就會生成一條詳細的日誌消息,其中包含有關定義裝飾器函數的確切位置,堆棧跟蹤收集位置等的信息.Wrapper對象還提供了一個方便的__repr__函數,其中包含有關生成器的詳細信息。
爲了更好地與現有框架(如Tornado,見[13])和編譯器(如Cython,見[16])集成,增長了兩個新的抽象基類(ABC):
注意,「基於生成器的協程」(有CO_ITERABLE_COROUTINE標識)並不實現__await__方法,所以它們不是collections.abc.Coroutine和collections.abc.Awaitable的實例:
@types.coroutine def gencoro(): yield assert not isinstance(gencoro(), collections.abc.Coroutine) # however: assert inspect.isawaitable(gencoro())
爲了便於測試對象是否支持異步迭代,還添加了兩個ABC: