Python 3、yield 表达式return value 受其刚刚通过send() 收到的值的影响?

Python 3, yield expression return value influenced by its value just received via send()?

阅读文档、问题并编写自己的测试代码后,我相信我已经理解了 yield expression 的工作原理。

然而,我对以下示例代码的行为感到惊讶:

def gen(n=0):
    while True:
        n = (yield n) or n+1

g=gen()
print( next(g) )
print( next(g) )
print( g.send(5) )
print( next(g) )
print( next(g) )

我原以为它 returned 0, 1, 2, 5, 6,而它却产生:0, 1, 5, 6, 7.

即:我本以为 yield expression 会产生这些效果:

  1. 计算 yield expression 的值,并将其 return 返回给调用者
  2. 从调用者的 send() 中获取值并将它们用作生成器函数代码接收的 yield 表达式的值
  3. 在执行任何其他操作之前暂停执行;它将在相同的 next(g)g.send() 调用
  4. 的相同点恢复

... and/or Python 会注意避免两者之间的任何干扰 (1) 和 (2) 中的信息流,即保证它们是独立的,例如在元组赋值 a, b = f(a,b), g(a,b)

(我什至想知道是否让暂停发生在(1)和(2)之间更好,但也许它会非常复杂,因为它意味着只有部分语句被执行并且休息留到下一份简历)

反正运算的顺序是(2),然后(1),再(3),所以(2)的赋值发生在(2)之前,可以影响(1)的赋值。 IE。 g.send() 调用注入的值在计算 yield 表达式本身之前使用,它作为相同 g.send() 表达式的值直接暴露给调用者。

我很惊讶,因为从生成器表达式中的代码的角度来看,在其 lhs 中接收的值会影响 rhs!

所接收的值

对我来说,这是一种误导,因为人们期望在像 lhs expr = rhs expr 这样的语句中,rhs expr 中的所有计算都在执行赋值之前完成,并在赋值期间冻结。一个赋值的 lhs 可以影响它自己的 rhs 看起来真的很奇怪!

问题:为什么会这样?有什么线索吗?

(我知道"We prefer questions that can be answered, not just discussed",但这是我偶然发现的事情,让我浪费了很多时间。我相信一些讨论不会有任何坏处,也许会拯救别人的时间)

PS。当然我明白我可以把赋值分成两步,这样从 send() 收到的任何值都只会在恢复操作后使用。像这样:

def gen(n=0):
    while True:
        received = (yield n)      
        n = received or (n+1)

您的困惑在于 generator.send()。发送 与使用 next() 相同,区别在于 yield 表达式产生不同的值。换句话说,next(g)g.send(None) 是一回事,这两个操作都会立即恢复生成器。

请记住,生成器 starts paused,位于顶部。第一个 next() 调用前进到第一个 yield 表达式,停止生成器然后暂停。当 yield 表达式暂停并且您调用 next(g)g.send(..) 时,生成器会在当前位置恢复,然后运行直到到达下一个 yield 表达式,此时它再次暂停。

对于您的代码,会发生这种情况:

  • g 已创建,gen()
  • 中没有任何反应
  • next(g)实际进入函数体,n = 0执行,yield n暂停g并产生0。这是打印出来的。
  • next(g) 恢复生成器; Noneyield n 返回(毕竟没有发送任何内容),因此执行 None or n + 1 并设置 n = 1。循环继续并再次到达 yield n ,生成器暂停并生成 1 。这是打印出来的。
  • g.send(5) 恢复生成器。 5 or n + 1表示执行n = 5。循环继续,直到达到 yield n,生成器暂停,生成 5 并打印 5.
  • next(g) 恢复生成器; None 返回(没有再次发送),因此执行 None or n + 1 并设置 n = 6。循环继续并再次到达 yield n,生成器暂停并生成并打印 6
  • next(g) 恢复生成器; None 返回(没有再次发送),因此执行 None or n + 1 并设置 n = 7。循环继续并再次到达 yield n,生成器暂停并生成并打印 7

鉴于您的步骤 1.、2. 和 3.,实际顺序是 3.、2.、1. 然后,另外 next() 还经过步骤 2. 生成None,并且 1. 下一个 调用 yield 在取消暂停后遇到。