关于Python生成器如何工作的困惑

时间:2018-01-05 06:59:40

标签: python generator

以下是Python书籍的副本,关于生成器如何工作的解释对我来说根本不清楚。

gen.send.py

def counter(start=0): 
    n = start 
    while True: 
        result = yield n # A 
        print(type(result), result) # B 
        if result == 'Q': 
            break 
        n += 1 

c = counter() 
print(next(c)) # C 
print(c.send('Wow!')) # D 
print(next(c)) # E 
print(c.send('Q')) # F 

以上的输出是:

$ python gen.send.py 
0 
<class 'str'> Wow! 
1 
<class 'NoneType'> None 
2 
<class 'str'> Q 
Traceback (most recent call last): 
  File "gen.send.py", line 14, in <module> 
    print(c.send('Q')) # F 
StopIteration
Learning Python. . VitalBook file.

书中的解释:

我们通过调用next(#C)来启动生成器执行。在生成器内,n设置为相同的start值。输入while循环,执行停止(#A),n(0)返回给调用者。控制台上印有0。

#Q1:此时,n = 1,对吗?因为执行打印(类型(结果),结果),所以应该执行n + = 1。

然后我们调用send(#D),执行恢复,结果设置为'哇!' (仍然是#A),然后它的类型和值打印在控制台上(#B)。结果不是'Q',因此n增加1并且执行返回到while条件,其为True,计算结果为True(这不难猜测,对吧?)。另一个循环周期开始,执行再次停止(#A),并且n(1)返回给调用者。 1将打印在控制台上。

Q2:'哇!'发给谁? n,开始还是结果?如何?如果n ='哇!'然后n + = 1的后果是什么?

此时,我们调用next(#E),再次恢复执行(#A),并且因为我们没有显式向生成器发送任何内容,所以Python的行为与不使用return语句的函数完全相同: yield n expression(#A)返回None。

问题3:为什么没有?其值(start,n,result)完全在此生成器中暂停

因此

结果设置为None,其类型和值再次打印在控制台上(#B)。执行继续,结果不是'Q'所以n增加1,我们再次开始另一个循环。执行再次停止(#A)并且n(2)返回给调用者。 2在控制台上打印。

问题4:为什么2?为什么不是4或5因为n + = 1语句?

现在为了最后的结局:我们再次调用send(#F),但这次我们传入'Q',因此当执行恢复时,结果被设置为'Q'(#A)。它的类型和值打印在控制台(#B)上,最后if子句的计算结果为True,while循环由break语句停止。生成器自然终止,这意味着引发了StopIteration异常。您可以在控制台上打印的最后几行看到其回溯的打印。

提前致谢。

4 个答案:

答案 0 :(得分:1)

解决此类学习问题的一种方法是可视化每一步发生的事情。让我们从头开始:

src
->app folder
main.ts
system.config.js
tsconfig.js

这里没有什么有趣的事情发生。它只是一个函数定义。

def counter(start=0): 
    n = start 
    while True: 
        result = yield n # A 
        print(type(result), result) # B 
        if result == 'Q': 
            break 
        n += 1

通常它应该执行该功能吗?但由于存在c = counter() 关键字,因此只需返回 yield即可用于执行该功能。再次阅读上一句话!这是第一个要理解的object

generators

这是使用对象print(next(c)) # C 执行函数的方式。您不能使用c调用它,而是执行()。这是执行指令的第一次时间,直到找到下一个next(c)语句为止。由于此时为yieldA的值为n,因此它只打印0并退出函数 - 最好说函数<强>暂停在这里。记住它甚至没有达到0!这回答了你的 Q1

n +=1

现在发生了一些更有趣的事情。之前停在print(c.send('Wow!')) # D 的生成器c现在只是恢复,它必须执行的下一个立即指令是yield n result =语句result = yield n }}。 给予A的值!所以现在send刚刚发生。

其余的执行是正常的。当它到达下一个result = 'Wow'时,它再次出现在函数中。现在yield nn,因为它在n+1循环中递增。我希望你能猜出代码的其余部分是如何运作的。

while

现在这个语句有些不同,因为它实际上打破了循环的值print(c.send('Q')) # F ,在这种情况下又会阻止任何进一步的sends。由于生成器不再找到任何yields语句,因此只会引发yield异常并停止。如果StopIteration循环之外有yield,它会返回并再次暂停。

答案 1 :(得分:0)

Q1:不确定这里的问题是什么

Q2:'Wow!'已发送至result

问题3:resultNone,因为执行已使用next()恢复,因此没有任何内容发送到yield表达式。因此,会发送默认值None

问题4:为什么要打印45n += 1只执行了两次。

答案 2 :(得分:0)

Q1 :不,此时n仍为0。发电机在A处停止运行,外部代码在C处打印出第一个产生的值。在将send作为D行的一部分调用之前,发电机不会开始运行。

Q2 :字符串"Wow!"成为行A中yield表达式的值,因此它将被分配给生成器中的result。它及其类型将在B行打印出来,这是您的第二行输出。然后n递增,循环重新开始,n1)从c.send获得返回值。对于第三行输出,它将打印在D行上。

Q3 :您可以通过调用next来恢复E行上的生成器,这相当于c.send(None)。因此result获取值None(类型为NoneType),并在生成器代码中打印。

Q4 :我不确定我明白你在这里问的是什么。您似乎了解执行流程。代码永远不会打印更多数字,因为生成器已经结束。它增加n两次,但在获得Q后,它会退出。

对于它的价值,你不太可能需要像这个例子那样编写代码。将next次通话与send次通话混合起来非常罕见(除了您将在其余时间打电话发送的协程上的第一个next)。

答案 3 :(得分:0)

yield视为特殊的return语句。当你到达result = yield n行时,首先执行右边,返回n,即0。与return的区别在于函数不会停止,它会暂停,所以下次您致电c.send(17)next(c)时,如果您使用{{1},它将从收益中恢复,将其替换为您发送的值(17)或None }} 办法。因此,当您第一次调用next(c)时,它会返回next(c)并暂停,当您调用0时,它会继续从生成器内打印类型和值c.send('Wow!'),然后返回send暂停,然后继续。

也许如果你将字母添加到print语句中,你可以更容易地看到每个输出行的来源:

1

这将输出:

def counter(start=0):
    n = start
    while True:
        result = yield n # A
        print("B:", type(result), result) # B
        if result == 'Q':
            break
        n += 1

c = counter()
print("C:", next(c)) # C
print("D:", c.send('Wow!')) # D
print("E:", next(c)) # E
print("F:", c.send('Q')) # F

回答你的问题:

  1. $ python gen.send.py C: 0 B: <class 'str'> Wow! D: 1 B: <class 'NoneType'> None E: 2 B: <class 'str'> Q Traceback (most recent call last): File "gen.send.py", line 14, in <module> print("F:", c.send('Q')) # F StopIteration 但在屈服n = 0后停顿了一下。打印来自n行。使用print(next(c)) # C恢复生成器后,n += 1将执行。
  2. 您传递给c.send('Wow!')方法的工作方式就好像它替换了生成器暂停的产量,在这种情况下send - &gt; result = yield n,因此传递给result = 'Wow!'
  3. 执行result时相当于执行next(c),因此会恢复执行,取代c.send(None)None - &gt; result = yield n的收益率
  4. 你唤醒了发电机4次:第一个result = None没有到达任何next(c); n += 1达到了一次;第二个c.send('Wow!')又到达了它;当next(c)语句执行退出c.send('Q')循环时break没有达到它。