请教 StopIteration 为什么能捕获到结果

2022-04-03 23:32:25 +08:00
 plko345

这是个计算斐波那切数列的代码, 为了说明 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

2070 次点击
所在节点    Python
5 条回复
LeeReamond
2022-04-04 06:20:19 +08:00
看了看 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 ,后面的就很好理解了。
plko345
2022-04-04 10:45:00 +08:00
@LeeReamond 多谢大佬, 我大概知道 value 最终赋值的是你说的 `input` 了, 而不是 Thing 实例, 但为什么 StopIteration 会等于 value 呢?
plko345
2022-04-04 11:12:35 +08:00
@LeeReamond 是不是 __await__ 的 return 就是 StopIteration 的值? 这样理解对吗? 可是为什么呢?
LeeReamond
2022-04-04 11:38:16 +08:00
@plko345 Thing 对象第二次被击穿的时候向它 send 了一个数,然后 return 了这个数,本质起到中转作用,thing_interceptor 充当事件循环,每次当它获取控制权时,负责不断向协程对象 send 以维持程序运行。程序除了顶层协程做根外,其余的中转全部在内部生成和消化,不会向事件循环上浮,最后递归返回后,根协程向事件循环返回常数,被包装在 StopIteration 里。你的注释有一些错误
LeeReamond
2022-04-04 11:48:43 +08:00
@plko345 不,这个理解不对,__await__的 return 值必须是可迭代对象,它会被多次激活,所以行为不能理解成一般方法的顺序执行并返回。在这个例子中,他的第一次 await 行为可以理解为(预激前)返回了生成器并预激,此后每次被 await 调用时步进生成器,hacking 将生成器执行逻辑和定义逻辑重合引起误解。我在 1L 最后一句话指根协程返回后上浮,并不是__await__的 return 值产生了 StopIteration ,表述不准确也误导了你

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/844810

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX