Python - yield(yield)是什么意思?

9
自从 Python 2.5 开始,生成器支持 send()throw()close()。在定义的生成器中,可以通过类似以下方式“捕获”发送的数据:
def gen():
    while True:
        x = (yield)
        if x == 3:
            print('received 3!!')
            break
        else:
            yield x

我将尝试介绍的是如何实现以下操作:

我想要做的是:

def gen2():
    while True:
        yield (yield)

注意,这是一个合法的生成器,它可以完成某些操作...

首先我要弄清楚的是:

这样的写作方式有什么好处?

当进行以下操作时,也要注意:

g = gen2()
next(g)
g.send(10) # output: 10
g.send(2) # output: nothing
g.send(3) # output: 3
g.send(44) # output: nothing

为什么每秒钟的“send”操作不起作用?

难以复现:当我将gen2替换为gen时,在send(3)处出现了“StopIteration”。 - Jean-François Fabre
你的问题是“yield(产出)是什么?”(我可以回答),还是“为什么每秒钟的'send'都没有做任何事情?”(我无法回答)。 - Jean-François Fabre
@Jean-FrançoisFabre 我完全明白他所展示的内容。 - Stefan Pochmann
1
针对您在各种.send调用的示例中,您是否指的是在交互式提示符下逐个执行这些命令时发生的“输出”?如果您以非交互方式运行代码,则根本不会有任何输出。 - BrenBarn
2个回答

10

yield (yield)首先从内部的yield产生None。然后接收来自sendnext的值。内部的yield将计算为此接收到的值,外部的yield会立即产生该值。


每个yield在概念上都有两个部分:

  1. sendnext的调用者传递一个值。
  2. 从下一个sendnext调用接收一个值。

同样,每个sendnext在概念上都有两个部分:

  1. 将一个值传递给生成器当前暂停的yield表达式。(对于next,这个值为None。)
  2. 从下一个yield表达式接收一个值。

系统最令人困惑的部分可能是这些部分交错了。一个yield的两个部分对应于两个不同的sendnext调用,而一个sendnext的两个部分对应于两个不同的yield

如果我们通过一个简单的例子来理解:

def gen():
    print('Not ran at first')
    yield (yield)

g = gen()  # Step 1
print(next(g))  # Step 2
print(g.send(1))  # Step 3
g.send(2)  # Step 4

以下是事情的处理方式:

Inside the generator                      Outside the generator

步骤1

                                          g calls gen()
g returns a generator object 
without executing the print
just yet statement.
                                          >>> g
                                          <generator object gen at 0x7efe286d54f8>

第二步

                                          next(g) sends None to g
g receives None, ignores it
  (since it is paused at the start
   of the function)

g prints ('not ran at first')

g executes the "transmit" phase
  of the inner yield, transmitting
  None
                                          next(g) receives None

步骤三

                                          g.send(1) sends 1 to g
g executes the "receive" phase
  of the inner yield, receiving 1
g executes the "transmit" phase
  of the outer yield, transmitting 1
                                          g.send(1) receives 1 from g

第四步

                                          g.send(2) sends 2 to g
g executes the "receive" phase
  of the outer yield, receiving 2
g reaches the end of gen and raises
  a StopIteration
                                          g.send(2) raises the StopIteration
                                          from g

3
yield是一个表达式。该表达式的值是使用.send发送的任何内容的值,如果没有发送任何内容(包括使用next而不是.send),则返回None。 .send是一种方法调用,因此也会返回一个值,这个值是生成器产生的值。换句话说,每次你使用.send时,都会产生一个值(可能为None),每次你使用yield时,都会发送一个值(可能为None)。

以下是一个简单的示例:

def gen():
    sent1 = yield 1
    print(sent1, "was sent")
    sent2 = yield 2
    print(sent2, "was sent")
    print("Reached end of generator")

g = gen()
print(next(g), "was yielded")
print(g.send("A"), "was yielded")
print(g.send("B"), "was yielded")
next(g)

# output
1 was yielded
A was sent
2 was yielded
B was sent
Reached end of generator
# StopIteration is raised here

在你的例子中,第一个next返回None,因为第一个yieldyield (yield)中的内部yield(即括号内的那个)。第一个send将10作为此yield的值传递。您发送的每个后续值都成为一个yield的值。之所以有些send调用没有输出,是因为内部yield未指定值,因此它会产生None。如上所述,当您调用send时,会产生一个值;在您的情况下,对于内部yield,该值为None,因此交互式提示未显示任何输出。另一方面,外部yield确实指定了一个值,即内部yield的结果。因此,当您向内部yield发送一个值时,它将由下一次迭代中的外部yield产生。 (我假设您指的是交互式提示的输出;如果将代码作为脚本运行,则根本不会有输出,因为您从未打印任何内容或以其他方式生成显式输出。)
下面是另一个可能有启发性的例子:
def gen():
    yield (yield (yield (yield "WHOA")))

>>> g = gen()
>>> next(g)
'WHOA'
>>> g.send(1)
1
>>> g.send(2)
2
>>> g.send(3)
3
>>> g.send(4)
Traceback (most recent call last):
  File "<pyshell#11>", line 1, in <module>
    g.send(4)
StopIteration

注意每次输入值时,它会立即被产出。这是因为每个yield都会产出更深层的yield的值。每个yield“成为”发送的值,并立即由链中的下一个yield产出。 这将一直持续到所有产出完成并引发StopIteration。

此前也有类似的问题。我的印象是,混淆往往会因为人们期望send“只发送”一个值而产生。但实际情况并非如此。使用send可以推进生成器并产出下一个结果,就像使用next一样。您可以认为next(gen)等同于gen.send(None)


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