【问题标题】:Python: I don't understand what's happening with this generatorPython:我不明白这个生成器发生了什么
【发布时间】:2012-10-18 09:13:16
【问题描述】:

我很好奇这里发生了什么。知道生成器和协程的人能否很好地解释一下这段代码。

def b():
    for i in range(5):
        yield i
        x = (yield)
        print(x)

def a():
    g = b()
    next(g)
    for i in range(4):
        g.send(5)
        print(next(g))

a()

输出

None
1
None
2
None
3
None
4

但是当我切换第 3 行和第 4 行:yield ix = (yield) 行时,我得到以下信息。

5
None
5
None
5
None
5
None

我怀疑问题可能是我尝试使用 yield 语句在同一个函数中接收和发送值。这在 Python 中是不可能的吗?

我已经成功编写了几个使用协程的程序,所以我熟悉它们的工作方式,但我对这段 sn-p 代码的行为方式感到困惑。对此的任何见解将不胜感激。

谢谢

编辑:感谢 BrenBarn 和 unutbu 的回答。当您将问题展开时,这里发生的事情会更有意义。

def b():
    for i in range(5):
        yield i
        x = yield None

def a():
    g = b()
    print('* got', g.send(None) )
    for i in range(4):
        print('+ got', g.send(5) )
        print('- got', g.send(None))

a()

【问题讨论】:

    标签: python generator coroutine


    【解决方案1】:

    使用traceit逐行遍历程序:

    import sys
    import linecache
    
    class SetTrace(object):
        '''
        with SetTrace(monitor):
            ...
        '''
        def __init__(self, func):
            self.func = func
        def __enter__(self):
            sys.settrace(self.func)
            return self
        def passit(self, frame, event, arg):
            return self.passit
        def __exit__(self, ext_type, exc_value, traceback):
            sys.settrace(self.passit)
    
    def traceit(frame, event, arg):
        '''
        http://www.dalkescientific.com/writings/diary/archive/2005/04/20/tracing_python_code.html
        '''
        if event == "line":
            lineno = frame.f_lineno
            filename = frame.f_globals["__file__"]
            if (filename.endswith(".pyc") or
                filename.endswith(".pyo")):
                filename = filename[:-1]
            name = frame.f_globals["__name__"]
            line = linecache.getline(filename, lineno)
            print("%s  # %s:%s" % (line.rstrip(), name, lineno, ))
        return traceit       
    
    def b():
        for i in range(5):
            yield i
            x = (yield)
            print(x)
    
    def a():
        g = b()                    
        next(g)
        for i in range(4):
            g.send(5)
            print(next(g))
    
    with SetTrace(traceit):
        a()        
    

    我们得到

    g = b()  # __main__:44
    next(g)  # __main__:45                 # runs b until you get to a yield
    for i in range(5):  # __main__:38
        yield i  # __main__:39             # stop before the yield; resume a
        ^
    for i in range(4):  # __main__:46
        g.send(5)  # __main__:47           # resume b; (yield i) expression evals to 5 then thrown away
        x = (yield)  # __main__:40         # stop before yield; resume a
             ^
        print(next(g))  # __main__:48      # next(g) called; resume b; print not called yet
        print(x)  # __main__:41            # next(g) causes (yield) to evaluate to None
    None
    for i in range(5):  # __main__:38
        yield i  # __main__:39             # yield 1; resume a; `print(next(g))` prints 1
    1
    for i in range(4):  # __main__:46
        g.send(5)  # __main__:47           # resume b; (yield i) expression evals to 5 then thrown away
    

    右侧(上图)的 cmets 解释了 Python 打印 None 然后是 1 的原因。如果你走到了这一步,我想你为什么会得到None2 等就很清楚了——这又是同一个故事,i 的值不同。

    另一个场景,x = (yield)yield i 是反向的,可以类似地分析。

    【讨论】:

      【解决方案2】:

      我不太明白你在问什么,但基本上:当你使用 send 时,它会导致生成器中最近到达的 yield 表达式计算为你发送的值。还要注意send 将生成器推进到下一个yield。可能让您感到困惑的一件事是您在生成器中打印x 的值,并且您在b 中打印next(g) 的值,但生成器也在g.send(5) 处产生值,并且你没有打印那些。

      在您的第一种情况下,您的第一个 send 导致 yield i 语句在 b 内计算为 5,但您不会在 b 内使用此值(您不会将 yield i 分配给任何东西),所以它什么也不做。此外,当您执行 send(5) 时,生成器会产生 None(来自 x = (yield) 行),但您不打印它,所以您不知道这一点。然后使用next(g) 再次推进生成器。最近达到的产量是x = yield,但next(g) 不传递任何值,因此x 设置为无。

      在第二种情况下,调用的奇偶校验是相反的。现在你的第一个send 确实发送到x = yield 行,所以x 设置为5。这个send 也产生b 中的循环值,但是你忽略a 中的这个值并且不要'打印它。然后打印next(g),即无。在随后的每次发送中,b 打印 x 的值,它始终为 5,因为这是您始终发送的值,然后 a 打印 next 产生的值,它始终为 None (因为这就是 x = yield 产生的结果)。

      我不太明白你所说的“使用 yield 语句在同一个函数中接收和发送值”是什么意思。您当然可以这样做,但您必须意识到:a) 即使您调用 next(g),仍然会发送一个值 (None); b) 当您调用 g.send(5) 时,仍然会产生一个值。

      【讨论】:

      • 谢谢,正如你所说,我很困惑,因为我没有打印g.send(5) 的结果。我也忘了x = yieldx = yield None是一样的。
      猜你喜欢
      • 2021-10-24
      • 2018-01-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多