Python:我不明白这个生成器发生了什么

时间:2012-10-29 02:18:41

标签: python generator coroutine

我很好奇这里发生了什么。知道生成器和协同程序的人可以很好地解释这段代码。

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中不可能吗?

我已经成功编写了几个使用协同程序的程序,因此我对它们的工作方式很熟悉,但我对这段代码的表现方式感到困惑。任何有关这方面的见解将不胜感激。

由于

编辑:感谢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()

2 个答案:

答案 0 :(得分:5)

使用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

右侧的评论(上图)解释了为什么Python打印None然后1。如果你走得那么远,我认为很清楚为什么你会得到None2等等 - 这是i的不同值的同一个故事。

可以类似地分析x = (yield)yield i相反的另一种情况。

答案 1 :(得分:4)

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

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

在第二种情况下,呼叫的奇偶校验是相反的。现在,您的第一个send会发送到x = yield行,因此x设置为5.此send也会产生b中的循环值,但是您忽略a中的此值,不要打印它。然后打印next(g),即无。在每次后续发送中,b打印x的值,该值始终为5,因为这是您始终发送的内容,然后a打印 next 产生的值,总是无(因为这是x = yield产生的)。

我不太明白你的意思是“使用yield语句来接收和发送同一个函数中的值”。你当然可以做到这一点,但你必须意识到:a)即使你打电话给next(g),仍然会发送一个值(无);和b)当你致电g.send(5)时,仍会产生一个价值。