yield内部的yield是什么意思?

61

请考虑以下代码:

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

输出结果为:

1
None

解释器在“外部”yield时确切地做了什么?


3
这是一篇经典的非常好的讲话,但现在已经被async/await所取代,可以解释它(现有的答案也很好!):https://www.dabeaz.com/coroutines/Coroutines.pdf - Benjamin Gruenbaum
4个回答

55

a 是一个生成器对象。第一次调用它的 next 方法,将会执行到第一个 yield 表达式(也就是最先被执行的内部表达式)。该 yield 将产生值为 1 的结果,并阻塞直到下一次进入生成器。这是由第二个对 next 的调用产生的,它不会向生成器发送任何值。因此,第一个(内部)的yield 的结果为 None。该值被用作外部的 yield 的参数,成为第二次对 next 的返回值。如果你再调用 next 第三次,你将得到一个 StopIteration 异常。

与使用 next 方法相比,使用 send 方法可以改变第一个 yield 表达式的返回值。

>>> 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关键字提供的值将作为next的返回值。生成器还会在yield语句处暂停,等待下一次next调用以继续执行。
在Python 2.5中,yield语句被替换为yield 表达式,并且生成器获得了一个send方法。sendnext非常相似,但它可以接受一个参数。(在接下来的内容中,假设next(a)等价于a.send(None))。在调用send(None)后,生成器开始执行,执行到第一个yield时返回一个值,与以前一样。现在,表达式会阻塞,直到下一次send调用,在这一点上,yield表达式将计算为传递给send的参数。生成器现在可以在恢复执行时接收一个值。

3
您似乎将“first”解释为“inner”。尽管内部的yield首先被计算,但它是第二个写入的,因此我认为该术语有些混淆。此外,我想您所说的“genenerator”应该是“generator”。 - Acccumulation

28

yield有两种形式,表达式和语句。它们大多数情况下是相同的,但我最常见到的是在语句形式中,其中结果不会被使用。

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

要了解语句和表达式的差异,在其他stackoverflow问题中有很好的答案:Python中表达式和语句的区别是什么?


1
很棒的答案 - 我想知道是否有人知道这种语法的实际应用。 - Jamie Counsell
1
允许生成器接收一个值使其非常接近于协程;唯一的区别是生成器不能指定在离开生成器主体时控制流去哪里;你总是回到控制流来自的地方。 - chepner

4
>>> 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
>>>

其他的生成器仅等待传入值,而生成器不仅提供数据,还接收数据。


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

1
任何生成器都会耗尽元素,直到它们用完为止。
在下面的2级嵌套示例中,第一个next给出了来自最内部yield的元素,即1,接下来的yield只返回None,因为它没有要返回的元素,如果再次调用next,它将返回StopIteration
def mygen():
     yield (yield 1)
a = mygen()
print(next(a))
print(next(a))
print(next(a))

你可以将此案例扩展以包含更多嵌套的yield,然后您会看到在调用n个next后,抛出StopIteration异常。以下是一个具有5个嵌套yield的示例。
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))

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接