摘要:
- 針對PEP 492的部分內容進行了翻譯
- 主要講述了 Python 的
async
,await
關鍵字
規範
這個提案介紹了新的語法和語義來增強Python 對協程的支持。這個規範假設你擁有 Python 協程實現的相關知識(PEP 342 和 PEP 380)。這個語法改變的動機是在 asyncio 框架(PEP 3156)和 協作函數 提案(PEP 3152,爲了有利於本規範現在被拒絕了)。
從文檔這一點我們使用 原生協程 的關鍵字去索引使用新語法生成的函數。基於生成器的協程 關鍵字用來描述基於生成器語法生成的協程。協程 關鍵字根據上下文,在兩種語義中都適用。
新的生成器聲明語法
如下的新語法用來去生成一個新的 原生協程 :
async def read_data(db):
pass
協程的關鍵屬性是:
async def
定義的函數總是協程,甚至它們不包含await
表達式- 在
async
函數中有yield
或者yield from
關鍵字會拋出SyntaxError
- 在內部,兩個新的代碼對象標記被使用:
CO_COROUTINE
用來去標記 原生協程 (使用新語法定義的)CO_ITERABLE_COROUTINE
用來去生成 基於生成器的協程 和 原生協程兼容 (由types.coroutine()
函數來設定)
- 通常生成器在被調用的時候返回一個 生成器對象,相似的,協程返回一個 協程 對象。
StopIteration
異常不會在協程外傳播,而是替換成了RuntimeError
。普通的生成器如果要這種行爲需要一個future import
(參考 PEP 479)- 當一個協程被GC回收時,如果它沒有被
await
的話它將會拋出一個RuntimeWaring
。(參考 Debugging Features) - 這部分內容也可以在Coroutine objects部分看到
types.coroutine()
這是一個在 types 模塊中新添加的方法coroutine(fn)
。它允許在已經存在的異步IO中 基於生成器的協程 和 本PEP介紹的 原生協程 之間進行交互。
@types.coroutine
def process_data(db):
data = yield from read_data(db)
...
這個函數將 CO_ITERABLE_COROUTINE
標誌應用到生成器函數代碼對象上,使它返回一個協程對象。
如果參數fn
不是一個生成器函數的話,它將會將它封裝。如果它返回了一個生成器,它將會將它封裝成爲一個 awaitable 的代理對象(參見下面 awaitable 對象的定義)。
注意,CO_COROUTINE
標記沒有被應用到types.coroutine()
上,這使得從基於生成器協程中分離出一個使用新語法定義的原生協程成爲可能。
Await 表達式
如下的新的await表達式用來獲取一個協程執行的結果。
async def read_data(db):
data = await db.fetch('SELECT ...')
...
await
相似於 yield from
,暫停 read_data
協程的執行,直到db.fetch()
這個 awaitable 對象完成並且返回數據。
它使用了 yield from
的實現,並添加上了額外的驗證參數的步驟。await
僅僅接收一個 awaitable 對象,它可以是如下幾種形式:
- 從一個 原生協程函數 返回的 原生協程 對象。
- 一個基於生成器協程 對象,並且使用了
types.coroutine()
修飾符。 - 一個對象擁有
__await__
方法並且返回了一個迭代器。
任何一個
yield from
鏈都會以一個yield
來結束。這是一個基本的 Futures 實現的機制。因爲,內部來說,協程是一種特殊類型的生成器。每個await
都被await
調用鏈下一級的某個yield
給暫停了(請參考 PEP 3156 查看更多的細節解釋)。
去爲協程開啓這個特性,一個叫做__await__
的新的魔法方法被添加了。在異步IO中,例如,去在 await 語句中開啓 Future 對象,僅僅需要做的改變就是爲asyncic.Future
類添加一行__await__ = __iter__
。
在本 PEP 的剩餘部分,擁有__await__
的對象就被叫做 Future-like 對象。
如果__await__
方法返回的不是一個迭代器,將會拋出一個TypeError
+ 使用CPython C API 函數tp_as_async.am_await
定義的對象將會返回一個迭代器(類似於__await__
方法)
如果在async def
函數外使用await
語句將總會拋出一個語法錯誤(就像在def
外部使用yield
語句)。
如果在await
語句後跟的不是一個awaitable對象,將會拋出一個TypeError
更新操作符優先級表
await
關鍵字是被如下定義的:
power ::= await ["**" u_expr]
await ::= ["await"] primary
primay 代表了語言中最緊緊綁定的操作符,它的語法是:
primary ::= atom | attributeref | subscription | slicing | call
參見 Python 文檔 Primaries 查看更多細節
關鍵字await
不同於yield
和yield from
操作符,await
表達式大部分時間不需要在他們周圍加上括號。
同樣的,yield from
允許一個表達式作爲它的參數,包括表達式像yield from a() + b()
,將會被解析成yield from (a() + b())
,這通常都是一個BUG。一般來說,任何算數運算的結果都不是一個awaitable
的對象。爲了避免這種類型的錯誤,我們決定讓await
比[]
,()
和.
有更低的優先級,但是比**
操作符的優先級要高。
Operator | Description |
---|---|
yield x , yield from x | Yield expression |
lambda | Lambda expression |
if – else | Conditional expression |
or | Boolean OR |
and | Boolean AND |
not x | Boolean NOT |
in , not in , is , is not , < , <= , > , >= , != , == | Comparisons, including membership tests and identity tests |
| | Bitwise OR |
^ | Bitwise XOR |
& | Bitwise AND |
<< , >> | Shifts |
|
Addition and subtraction |
|
Multiplication, matrix multiplication, division, remainder |
+x , -x , ~x | Positive, negative, bitwise NOT |
** | Exponentiation |
await x | Await expression |
x[index] , x[index:index] , x(arguments…) , x.attribute | Subscription, slicing, call, attribute reference |
(expressions…) , [expressions…] , {key: value…} , {expressions…} | Binding or tuple display, list display, dictionary display, set display |
“await” 表達式的例子
有效的語法例子:
表達式 | 表達式將會被解析成 |
---|---|
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()) |
無效的語法例子:
表達式 | 應該被寫成 |
---|---|
await await coro() | await (await coro()) |
await -coro() | await (-coro()) |
異步上下文管理器和 “async with”
一個異步的上下文管理器是一種特殊的上下文管理器,能夠在它的 enter 和 exit 方法中暫停。
爲了使這種設想成爲可能,一種新的協議用來支持異步的上下文管理器被提出了。兩個魔法方法被添加了,__aenter__
和__aexit__
。這兩個都必須返回一個awaitable
的對象。
一個異步的上下文管理器的例子是:
class AsyncContextManager:
async def __aenter__(self):
await log('enter 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)
exec = True
VAR = await aenter
try:
block
except:
if not await(mgr, *sys.exc_info()):
raise
else:
await aexit(mgr, None, None, None)
就像普通的with
語句一樣,async with
語句也能夠指定多個上下文管理器。
將一個普通的沒有__aenter__
和__aexit__
方法的上下文管理器傳遞給async with
語句是錯誤的。同時,在async def
函數外部使用async with
語句也是一個語法錯誤。
例子
使用異步的上下文管理器很容易去正確實現協程的數據庫事物管理:
async def commit(session, data):
...
async with sesson.transaction():
...
await session.update(data)
...
同時,代碼需要看起來更輕量級一些:
async with lock:
...
而不是:
with (yield from lock):
...
異步迭代器和 “async for”
一個異步可迭代對象能夠在它的迭代器實現中異步地調用代碼,異步迭代器能夠在它的next
方法中調用異步代碼,爲了支持異步迭代:
- 一個對象必須實現一個
__aiter__
方法(或者使用了 CPython 的C API 的tp_as_async.am_aiter
接口),返回一個異步迭代器對象。- 一個異步迭代器對象必須實現一個
__anext__
方法(或者也可以使用 CPython 的 C API 的tp_as_async.am_next
接口),返回一個awaitable
對象。- 爲了停止迭代,
__anext__
必須拋出一個StopAsyncIteration
異常。
一個異步迭代的例子如下所示:
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
對async for
語句拋出一個沒有__aiter__
方法的普通迭代器將會拋出一個TypeError
。在async for
外部使用async for
語句將會拋出一個SyntaxError
。
就像普通的for
語句一樣,async for
有可選的else
子句。
例子1
使用異步迭代協議可以在迭代期間異步緩存數據:
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 = Cursor().__aiter__()
while True:
try:
row = await i.__anext__()
except StopAsyncIteration:
break
else:
print(row)
例子2
如下的例子是一個使用類將普通迭代器轉換成異步的。雖然這並不是一個非常有用的事情,但是如下的代碼說明了普通迭代器和異步迭代器之間的關係:
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)
爲什麼是StopAsyncIteration
異常
協程對象內部仍然是基於生成器的。所以,在PEP 479
之前,在下面這兩種代碼中沒有基本的區別:
def g1():
yield from fut
return 'spam'
def g2():
yield from fut
raise StopIteration('spam')
因爲 PEP 479 被接受了,而且默認在協程對象中開啓,所以如下的例子將會把它的StopIteration
包裹在一個RuntimeError
中:
async def a1():
await fut
raise StopIteration('spam')
唯一的方式就是由迭代器告訴外部的代碼,迭代結束了而不能使用StopIteration
異常。因此,一個新的內建的異常類StopAsyncIteration
被添加了。
此外,由PEP 479
的語義可得,所有協程中的StopIteration
將會由一個RuntimeError
來包裹。