等待一个自定义类的对象会发生什么?
Python文档 指出:
await是Python的一个关键字,通常以两种不同的方式使用:
await task await coroutine在本质上,
await的行为取决于被等待对象的类型。
本节指出:
- 「coroutine」与「coroutine object」同义。
- 「coroutine object」通过调用「coroutine function」(也称为「异步函数」,这是同义词)来创建。
例如
async def loudmouth_penguin(magic_number: int):
print(
"I am a super special talking penguin. Far cooler than that printer. "
f"By the way, my lucky number is: {magic_number}."
)
obj = loudmouth_penguin(magic_number=3)
在这种情况下,loudmouth_penguin 是一个「coroutine function」(或「异步函数」),而 obj 是一个「coroutine object」(或「协程」)。
一个任务通过 asyncio.create_task 函数创建:
task = asyncio.create_task(coroutine)
回到 await:
- 等待一个任务会把控制权从当前任务或协程让渡给事件循环
- 与任务不同,等待一个协程不会把控制权交还给事件循环!
await coroutine的行为本质上等同于调用一个普通的、同步的Python函数。
接着我们有这个示例(我稍作修改,增加了一些打印语句以便了解发生了什么):
class Rock:
def __await__(self):
print(f"\t\tRock.__await__ called")
value_sent_in = yield 7
print(f"\t\tRock.__await__ resuming with value: {value_sent_in}.")
return value_sent_in
async def main():
print(f"\tBeginning coroutine main().")
rock = Rock()
print(f"\tAwaiting rock...")
value_from_rock = await rock
print(f"\tCoroutine received value: {value_from_rock} from rock.")
return 23
print(f"Calling coroutine")
coroutine = main()
intermediate_result = coroutine.send(None)
print(f"Coroutine paused and returned intermediate value: {intermediate_result}.\n")
print(f"Resuming coroutine and sending in value: 42.")
try:
coroutine.send(42)
except StopIteration as e:
returned_value = e.value
print(f"Coroutine main() finished and provided value: {returned_value}.")
运行它将返回:
Calling coroutine
Beginning coroutine main().
Awaiting rock...
Rock.__await__ called
Coroutine paused and returned intermediate value: 7.
Resuming coroutine and sending in value: 42.
Rock.__await__ resuming with value: 42.
Coroutine received value: 42 from rock.
Coroutine main() finished and provided value: 23.
在这种情况下,我们以文档从未解释过的方式使用 await。我们正在等待一个对象,即一个用户自定义类的实例(第 value_from_rock = await rock 行)。
对一个用户自定义类的实例执行 await 会怎样?我们不知道。文档没有说明。用户只能自行猜测。
大概,它会立即调用该类的 __await__ 成员函数并返回?(因此不把控制权推回事件循环)
所以
value_from_rock = await rock
在本质上等同于:
value_from_rock = rock.__await__()
yield value_from_rock
?
请注意,在执行完
value_from_rock = await rock
之后,下一行
print(f"\tCoroutine received value: {value_from_rock} from rock.")
在第一次调用协程时并不会执行。就像它在立即返回一样。
解决方案
await 起源于 yield from,因此如果对象具备相应的方法,它与 yield from custom_object.__await__() 同义(原生对象,例如实际的协程对象,内部有用于此的隐藏方法)。
语法和协议的完整起源最好由 PEP 492 解释:
The following new
awaitexpression is used to obtain a result of coroutine execution:
async def read_data(db): data = await db.fetch('SELECT ...') ...
await, similarly toyield from, suspends execution ofread_datacoroutine untildb.fetchawaitable completes and returns the result data.It uses the
yield fromimplementation with an extra step of validating its argument.awaitonly accepts an awaitable, which can be one of:
- A native coroutine object returned from a native coroutine function.
- A generator-based coroutine object returned from a function decorated with
types.coroutine().- An object with an
__await__method returning an iterator.Any
yield fromchain of calls ends with ayield. This is a fundamental mechanism of how Futures are implemented. Since, internally, coroutines are a special kind of generators, everyawaitis suspended by ayieldsomewhere down the chain ofawaitcalls (please refer to PEP 3156 for a detailed explanation).To enable this behavior for coroutines, a new magic method called
__await__is added. In asyncio, for instance, to enable Future objects inawaitstatements, the only change is to add__await__ = __iter__line toasyncio.Futureclass.Objects with
__await__method are called Future-like objects in the rest of this PEP.It is a
TypeErrorif__await__returns anything but an iterator. * Objects defined with CPython C API with atp_as_async.am_awaitfunction, returning an iterator (similar to__await__method).It is a
SyntaxErrorto useawaitoutside of anasync deffunction (like it is aSyntaxErrorto useyieldoutside ofdeffunction).It is a
TypeErrorto pass anything other than an awaitable object to anawaitexpression.