【问题标题】:What does a yield inside a yield do?收益中的收益有什么作用?
【发布时间】:2019-09-19 04:42:28
【问题描述】:

考虑以下代码:

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

输出产量:

1
None

解释器在“外部”究竟做了什么?

【问题讨论】:

标签: python python-3.x yield


【解决方案1】:

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

比较使用send方法(而不是next)来改变第一个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 方法。 send 的工作方式与next 非常相似,只是它可以接受参数。 (对于其余部分,假设next(a) 等价于a.send(None)。)生成器在调用send(None) 后开始执行,此时它执行到第一个yield,它返回一个值前。然而,现在表达式会阻塞,直到 next 调用send,此时yield 表达式的计算结果为传递给send 的参数。生成器现在可以在恢复时接收一个值。


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

【讨论】:

  • 您似乎在使用“first”来表示“inner”。虽然内部yield 首先是评估,其次是编写,所以我认为该术语令人困惑。另外,我假设“发电机”是指“发电机”。
【解决方案2】:

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

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

【讨论】:

  • 很好的答案 - 我想知道是否有人知道这种语法的实际应用程序。
  • @JamieCounsell 允许生成器接收值使其非常接近协程;唯一的区别是生成器在离开生成器主体时无法指定控制的去向;你总是回到控制来自来自的任何地方。
【解决方案3】:
>>> 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
>>>

【讨论】:

    【解决方案4】:

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

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

    你可以扩展这个case来包含更多的嵌套yield,你会看到nnext被调用之后,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))
    

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

    【讨论】:

      猜你喜欢
      • 2019-03-22
      • 2010-10-04
      • 2013-07-03
      • 2016-12-21
      • 2016-08-07
      • 2015-10-11
      • 2010-09-06
      • 2012-08-15
      相关资源
      最近更新 更多