V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
推荐学习书目
Learn Python the Hard Way
Python Sites
PyPI - Python Package Index
http://diveintopython.org/toc/index.html
Pocoo
值得关注的项目
PyPy
Celery
Jinja2
Read the Docs
gevent
pyenv
virtualenv
Stackless Python
Beautiful Soup
结巴中文分词
Green Unicorn
Sentry
Shovel
Pyflakes
pytest
Python 编程
pep8 Checker
Styles
PEP 8
Google Python Style Guide
Code Style from The Hitchhiker's Guide
plko345
V2EX  ›  Python

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

  •  
  •   plko345 · 2022-04-03 23:32:25 +08:00 · 2207 次点击
    这是一个创建于 968 天前的主题,其中的信息可能已经有所发展或是发生改变。

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

    5 条回复    2022-04-04 11:48:43 +08:00
    LeeReamond
        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 ,后面的就很好理解了。
    plko345
        2
    plko345  
    OP
       2022-04-04 10:45:00 +08:00
    @LeeReamond 多谢大佬, 我大概知道 value 最终赋值的是你说的 `input` 了, 而不是 Thing 实例, 但为什么 StopIteration 会等于 value 呢?
    plko345
        3
    plko345  
    OP
       2022-04-04 11:12:35 +08:00
    @LeeReamond 是不是 __await__ 的 return 就是 StopIteration 的值? 这样理解对吗? 可是为什么呢?
    LeeReamond
        4
    LeeReamond  
       2022-04-04 11:38:16 +08:00
    @plko345 Thing 对象第二次被击穿的时候向它 send 了一个数,然后 return 了这个数,本质起到中转作用,thing_interceptor 充当事件循环,每次当它获取控制权时,负责不断向协程对象 send 以维持程序运行。程序除了顶层协程做根外,其余的中转全部在内部生成和消化,不会向事件循环上浮,最后递归返回后,根协程向事件循环返回常数,被包装在 StopIteration 里。你的注释有一些错误
    LeeReamond
        5
    LeeReamond  
       2022-04-04 11:48:43 +08:00
    @plko345 不,这个理解不对,__await__的 return 值必须是可迭代对象,它会被多次激活,所以行为不能理解成一般方法的顺序执行并返回。在这个例子中,他的第一次 await 行为可以理解为(预激前)返回了生成器并预激,此后每次被 await 调用时步进生成器,hacking 将生成器执行逻辑和定义逻辑重合引起误解。我在 1L 最后一句话指根协程返回后上浮,并不是__await__的 return 值产生了 StopIteration ,表述不准确也误导了你
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5372 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 32ms · UTC 03:46 · PVG 11:46 · LAX 19:46 · JFK 22:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.