PEP 492 部分翻译
文章目录
摘要:
- 针对PEP 492部分进行了翻译
- 主要讲述了 Python 的
async
,await
关键字
规范
这个提案介绍了新的语法和语义来增强Python 对协程的支持。这个规范假设你拥有 Python 协程实现的相关知识(PEP 342 和 PEP 380)。这个语法改变的动机是在 asyncio 框架(PEP 3156)和 协作函数 提案(PEP 3152,为了有利于本规范现在被拒绝了)。
从文档这一点我们使用 原生协程 的关键字去索引使用新语法生成的函数。基于生成器的协程 关键字用来描述基于生成器语法生成的协程。协程 关键字根据上下文,在两种语义中都适用。
新的生成器声明语法
如下的新语法用来去生成一个新的 原生协程 :
|
|
协程的关键属性是:
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介绍的 原生协程 之间进行交互。
|
|
这个函数将 CO_ITERABLE_COROUTINE
标志应用到生成器函数代码对象上,使它返回一个协程对象。
如果参数fn
不是一个生成器函数的话,它将会将它封装。如果它返回了一个生成器,它将会将它封装成为一个 awaitable 的代理对象(参见下面 awaitable 对象的定义)。
注意,CO_COROUTINE
标记没有被应用到types.coroutine()
上,这使得从基于生成器协程中分离出一个使用新语法定义的原生协程成为可能。
Await 表达式
如下的新的await表达式用来获取一个协程执行的结果。
|
|
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
的对象。
一个异步的上下文管理器的例子是:
|
|
新的语法
针对异步的上下文管理器,一种新的语法被提出来了:
|
|
它在语义上等价于:
|
|
就像普通的with
语句一样,async with
语句也能够指定多个上下文管理器。
将一个普通的没有__aenter__
和__aexit__
方法的上下文管理器传递给async with
语句是错误的。同时,在async def
函数外部使用async with
语句也是一个语法错误。
例子
使用异步的上下文管理器很容易去正确实现协程的数据库事物管理:
|
|
同时,代码需要看起来更轻量级一些:
|
|
而不是:
|
|
异步迭代器和 “async for”
一个异步可迭代对象能够在它的迭代器实现中异步地调用代码,异步迭代器能够在它的next
方法中调用异步代码,为了支持异步迭代:
- 一个对象必须实现一个
__aiter__
方法(或者使用了 CPython 的C API 的tp_as_async.am_aiter
接口),返回一个异步迭代器对象。- 一个异步迭代器对象必须实现一个
__anext__
方法(或者也可以使用 CPython 的 C API 的tp_as_async.am_next
接口),返回一个awaitable
对象。- 为了停止迭代,
__anext__
必须抛出一个StopAsyncIteration
异常。
一个异步迭代的例子如下所示:
|
|
新的语法
一种通过异步迭代器进行迭代的新的语句被提出来了:
|
|
它在语义上等于:
|
|
对async for
语句抛出一个没有__aiter__
方法的普通迭代器将会抛出一个TypeError
。在async for
外部使用async for
语句将会抛出一个SyntaxError
。
就像普通的for
语句一样,async for
有可选的else
子句。
例子1
使用异步迭代协议可以在迭代期间异步缓存数据:
|
|
cursor
是一个异步迭代器,在N次迭代后从数据库中取出N行的数据。
如下的代码说明了一种新的异步迭代协议:
|
|
Cursor
类能够按下面的方式进行使用:
|
|
它将会等价于如下的代码:
|
|
例子2
如下的例子是一个使用类将普通迭代器转换成异步的。虽然这并不是一个非常有用的事情,但是如下的代码说明了普通迭代器和异步迭代器之间的关系:
|
|
为什么是StopAsyncIteration
异常
协程对象内部仍然是基于生成器的。所以,在PEP 479
之前,在下面这两种代码中没有基本的区别:
|
|
|
|
因为 PEP 479 被接受了,而且默认在协程对象中开启,所以如下的例子将会把它的StopIteration
包裹在一个RuntimeError
中:
|
|
唯一的方式就是由迭代器告诉外部的代码,迭代结束了而不能使用StopIteration
异常。因此,一个新的内建的异常类StopAsyncIteration
被添加了。
此外,由PEP 479
的语义可得,所有协程中的StopIteration
将会由一个RuntimeError
来包裹。