5

我正在玩生成器和生成器表达式,但我不完全确定我理解它们是如何工作的(一些参考资料):

>>> a = (x for x in range(10))
>>> next(a)
0
>>> next(a)
1
>>> a.send(-1)
2
>>> next(a)
3

所以看起来generator.send被忽略了。这是有道理的(我猜),因为没有明确yield的表达式来捕获发送的信息......

然而,

>>> a = ((yield x) for x in range(10))
>>> next(a)
0
>>> print next(a)
None
>>> print next(a)
1
>>> print next(a)
None
>>> a.send(-1)  #this send is ignored, Why? ... there's a yield to catch it...
2
>>> print next(a)
None
>>> print next(a)
3
>>> a.send(-1)  #this send isn't ignored
-1

我知道这还很遥远,而且我(目前)想不出一个用例(所以不要问;)

我主要是在探索试图弄清楚这些不同的生成器方法是如何工作的(以及生成器表达式一般是如何工作的)。为什么我的第二个示例在产生合理值和 之间交替None?另外,谁能解释为什么我generator.send的一个被忽略而另一个没有?

4

5 回答 5

3

您有点困惑,因为您实际上是从两个来源生成的:生成器表达式(... for x in range(10))是一个生成器,但您使用yield. 你可以看到,如果list(a)你会得到[0, None, 1, None, 2, None, 3, None, 4, None, 5, None, 6, None, 7, None, 8, None, 9, None]

您的代码等效于:

>>> def gen():
...     for x in range(10):
...         yield (yield x)

只有内部产量(“产量 x”)在生成器中被“使用”——它被用作外部产量的值。所以这个生成器在范围的产生值和产生任何“发送”到这些产生的值之间来回迭代。如果你向内部 yield 发送一些东西,你会得到它,但如果你碰巧在偶数迭代中发送,发送将被发送到外部 yield 并被忽略。

于 2012-09-07T19:25:57.320 回答
3

这里的困惑是生成器表达式正在执行隐藏yield。这是函数形式:

def foo():
    for x in range(10):
        yield (yield x)

当您执行 a.send()时,会发生内部yield x执行,这会产生x. 然后表达式计算为 的值.send,并且下一个 yield 产生该值。这是更清晰的形式:

def foo():
    for x in range(10):
        sent_value = (yield x)
        yield sent_value

因此输出是非常可预测的:

>>> a = foo()
#start it off
>>> a.next() 
0
#execution has now paused at "sent_value = ?"
#now we fill in the "?". whatever we send here will be immediately yielded.
>>> a.send("yieldnow") 
'yieldnow'
#execution is now paused at the 'yield sent_value' expression
#as this is not assigned to anything, whatever is sent now will be lost
>>> a.send("this is lost") 
1
#now we're back where we were at the 'yieldnow' point of the code
>>> a.send("yieldnow") 
'yieldnow'
#etc, the loop continues
>>> a.send("this is lost")
2
>>> a.send("yieldnow")
'yieldnow'
>>> a.send("this is lost")
3
>>> a.send("yieldnow")
'yieldnow'

编辑:示例用法。到目前为止,我见过的最酷的功能是扭曲的inlineCallbacks功能。请参阅此处以获取解释它的文章。它的核心是它允许您生成要在线程中运行的函数,一旦函数完成,twisted 会将函数的结果发送回您的代码。因此,您可以以非常线性和直观的方式编写严重依赖线程的代码,而不必到处编写大量的小函数。

有关使用潜在用例的基本原理的更多信息,请参阅PEP 342.send(我提供的扭曲示例是此更改提供的异步 I/O 的一个示例)。

于 2012-09-07T19:24:20.413 回答
2

该生成器转换为:

for i in xrange(10):
    x = (yield i)
    yield x

第二次调用 send()/next() 的结果将被忽略,因为您对 yield 之一的结果什么也不做。

于 2012-09-07T19:24:41.693 回答
0

您编写的生成器相当于更详细的:

def testing():
    for x in range(10):
            x = (yield x)
            yield x

正如您在此处看到的,yield生成器表达式中隐含的 second 不会保存您传递的值,因此取决于生成器执行被阻止的位置,send可能会或可能不会起作用。

于 2012-09-07T19:38:17.937 回答
-1

确实 - 该send方法旨在与生成器对象一起使用,该生成器对象是您明确编写的协同程序的结果。在生成器表达式中很难理解它的含义——尽管它有效。

-- 编辑 -- 我以前写过这个,但它是不正确的,因为生成器表达式中的 yield 在实现中是可预测的——尽管在任何 PEP 中都没有提到。

生成器表达式并不意味着具有yield关键字 - 我不知道在这种情况下甚至定义了行为。我们可以稍微思考一下,了解您的表情中正在发生的事情,以了解那些“无”的来源。但是,假设作为在 Python 中如何实现 yield 的副作用(并且可能它甚至依赖于实现),而不是应该如此。

生成器表达式的正确形式,以简化的方式是:

(<expr> for <variable> in <sequence> [if <expr>])

因此,对 -<expr> 中的每个值进行评估,<sequence:不仅yield不需要,因为您不应该使用它。

两者yieldsend方法都旨在用于完整的协同程序,例如:

def doubler():
   value = 0
   while value < 100:
       value = 2 * (yield value)

你可以像这样使用它:

>>> a = doubler()
>>> # Next have to be called once, so the code will run up to the first "yield"
... 
>>> a.next()
0
>>> a.send(10)
20
>>> a.send(20)
40
>>> a.send(23)
46
>>> a.send(51)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> 
于 2012-09-07T19:33:16.520 回答