yield 中的 yield 有什么作用?

What does a yield inside a yield do?

考虑以下代码:

def mygen():
     yield (yield 1)
a = mygen()
print(next(a))
print(next(a)) 

输出结果:

1
None

解释器在 "outside" 处究竟做了什么?

任何生成器都会耗尽元素,直到用完为止。
在下面的 2 级嵌套示例中,第一个 next 为我们提供了最内层 yield 的元素,即 1,下一个仅产生 returns None,因为它没有元素到 return,如果你再次调用 next,它将 return StopIteration

def mygen():
     yield (yield 1)
a = mygen()
print(next(a))
print(next(a))
print(next(a))

您可以扩展这种情况以包含更多嵌套收益,您会看到在调用 n next 之后,抛出 StopIteration 预期,下面是一个 5 的示例嵌套收益率

def mygen():
     yield ( yield ( yield ( yield (yield 1))))
a = mygen()
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))
print(next(a))

请注意,此答案仅基于我的观察,在技术细节上可能不正确,欢迎所有更新和建议

>>> def mygen():
...     yield (yield 1)
...
>>> a = mygen()
>>>
>>> a.send(None)
1
>>> a.send(5)
5
>>> a.send(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>
>>>
>>>
>>> def mygen():
...     yield 1
...
>>> def mygen2():
...     yield (yield 1)
...
>>> def mygen3():
...     yield (yield (yield 1))
...
>>> a = mygen()
>>> a2 = mygen2()
>>> a3 = mygen3()
>>>
>>> a.send(None)
1
>>> a.send(0)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a2.send(None)
1
>>> a2.send(0)
0
>>> a2.send(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> a3.send(None)
1
>>> a3.send(0)
0
>>> a3.send(1)
1
>>> a3.send(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

每个其他 yield 只是等待传入一个值,生成器不仅提供数据,而且还接收数据。


>>> def mygen():
...     print('Wait for first input')
...     x = yield # this is what we get from send
...     print(x, 'is received')
...
>>> a = mygen()
>>> a.send(None)
Wait for first input
>>> a.send('bla')
bla is received
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>>

yield得到下一个继续时给出下一个值,如果不用于给出下一个值,则用于接收下一个

>>> def mygen():
...     print('Wait for first input')
...     x = yield # this is what we get from send
...     yield x*2 # this is what we give
...
>>> a = mygen()
>>> a.send(None)
Wait for first input
>>> a.send(5)
10
>>>

a 是生成器对象。第一次对其调用 next 时,主体会被计算到第一个 yield 表达式(即第一个被计算的:内部表达式)。 yieldnext 生成值 1 到 return,然后阻塞直到生成器的下一个条目。这是由对 next 的第二次调用产生的,它 将任何值 发送到 生成器。结果,第一个(内部)yield 的计算结果为 None。该值用作外部 yield 的参数,它成为第二次调用 next 的 return 值。如果您第三次调用 next,则会出现 StopIteration 异常。

比较使用 send 方法(而不是 next)更改第一个 yield 表达式的 return 值。

>>> a = mygen()
>>> next(a)
1
>>> a.send(3)  # instead of next(a)
3
>>> next(a)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

编写生成器的更明确的方法是

def mygen():
    x = yield 1
    yield x

a = mygen()
print(a.send(None))  # outputs 1, from yield 1
print(a.send(5))     # makes yield 1 == 5, then gets 5 back from yield x
print(a.send(3))     # Raises StopIteration, as there's nothing after yield x

在 Python 2.5 之前,yield 语句 提供调用者和生成者之间的单向通信;对 next 的调用将执行生成器直到下一个 yield 语句,并且 yield 关键字提供的值将用作 return 值 next.发电机 也会在 yield 语句处暂停,等待下一次调用 next 恢复。

在 Python 2.5 中,yield 语句被替换*为 yield 表达式 ,并且生成器获得了 send 方法。 sendnext 非常相似,只是它可以带参数。 (对于其余部分,假设 next(a) 等同于 a.send(None)。)生成器在调用 send(None) 后开始执行,此时它执行到第一个 [=14] =],其中 returns 和以前一样是一个值。但是,现在表达式会阻塞,直到 下一个 调用 send,此时 yield 表达式计算为传递给 send 的参数。生成器现在可以在恢复时接收一个值。


* 没有完全取代; kojiro 的回答更详细地说明了 yield 语句和 yield 表达式之间的细微差别。

yield有两种形式,expressions and statements。它们大部分是相同的,但我最常在 statement 形式中看到它们,其中不会使用结果。

def f():
    yield a thing

但是在表达式形式中,yield有一个值:

def f():
    y = yield a thing

在您的问题中,您使用了两种形式:

def f():
    yield ( # statement
        yield 1 # expression
    )

当你迭代生成的生成器时,你首先得到内部 yield 表达式的结果

>>> x=f()
>>> next(x)
1

至此,内部表达式也产生了外部语句可以使用的值

>>> next(x)
>>>  # None

现在你已经耗尽了发电机

>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

要了解更多关于语句与表达式的信息,在其他 Whosebug 问题中有很好的答案:What is the difference between an expression and a statement in Python?