这是个计算斐波那切数列的代码, 为了说明 async/await 的使用, 但不明白为什么 StopIteration 会获得最终结果
class Thing:
def __init__(self, n):
self._n = n
def __await__(self): # Thing 是 iterator
return (yield self) # 这里每次迭代返回 Thing 对象实例自身?
def thing_interceptor(coro):
value_to_send = None
while True:
try:
thing = coro.send(value_to_send) # 把 value_to_send send 进去干嘛? 可不可以理解为没什么用, 等同于一次 next?
value_to_send = thing._n # value_to_send 的值是 1 或 0
except StopIteration as exc:
return exc.value
async def f(n):
if n <= 1:
value = await Thing(n)
else:
value = await f(n-2) + await f(n-1) # 为什么两个 Thing 能相加, 也没实现 add 啊?
return value # 最终返回 Thing 对象
coro = f(3)
print(thing_interceptor(coro))
这里我把原文的一些注释去掉了, 原文链接: https://gist.github.com/erikbern/ad7615d22b700e8dbbafd8e4d2f335e1
1
LeeReamond 2022-04-04 06:20:19 +08:00 1
看了看 gist 原文,标题叫 loop_hack ,很清晰地表达了代码需求。
__await__魔术方法必需要返回一个可迭代对象,如果直接返回 self 的话,由于 Thing 本身没实现__next__方法,并不是可迭代对象,这里利用了 py 的 yield 特性,凡定义 yield 的函数例如 func ,在调用 func()时区别于默认的返回 return 值,作为替代会返回 func 的生成器对象,解决了必须返回可迭代对象的问题。同理可以使用以下代码: ```Python class A: def __iter__(self): return (yield self) for _ in A(): ... ``` 因为是非常莫名其妙的写法,所以作者也在标题里写了这是 hacking 。但是由于这里少写了一个换行导致语义很难理解,如果我做 codereview 会直接枪毙,实际上就是 input=yield self 然后 return input ,即将该对象作为只能激活两次的可迭代对象,第一次返回 self 第二次上浮 StopIteration ,后面的就很好理解了。 |
2
plko345 OP @LeeReamond 多谢大佬, 我大概知道 value 最终赋值的是你说的 `input` 了, 而不是 Thing 实例, 但为什么 StopIteration 会等于 value 呢?
|
3
plko345 OP @LeeReamond 是不是 __await__ 的 return 就是 StopIteration 的值? 这样理解对吗? 可是为什么呢?
|
4
LeeReamond 2022-04-04 11:38:16 +08:00
@plko345 Thing 对象第二次被击穿的时候向它 send 了一个数,然后 return 了这个数,本质起到中转作用,thing_interceptor 充当事件循环,每次当它获取控制权时,负责不断向协程对象 send 以维持程序运行。程序除了顶层协程做根外,其余的中转全部在内部生成和消化,不会向事件循环上浮,最后递归返回后,根协程向事件循环返回常数,被包装在 StopIteration 里。你的注释有一些错误
|
5
LeeReamond 2022-04-04 11:48:43 +08:00
@plko345 不,这个理解不对,__await__的 return 值必须是可迭代对象,它会被多次激活,所以行为不能理解成一般方法的顺序执行并返回。在这个例子中,他的第一次 await 行为可以理解为(预激前)返回了生成器并预激,此后每次被 await 调用时步进生成器,hacking 将生成器执行逻辑和定义逻辑重合引起误解。我在 1L 最后一句话指根协程返回后上浮,并不是__await__的 return 值产生了 StopIteration ,表述不准确也误导了你
|