试图将收益率理解为一种表达方式

时间:2012-09-07 19:15:41

标签: python generator

我正在玩生成器和生成器表达式,我不完全确定我理解它们是如何工作的(some reference material):

>>> 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被忽略而另一个不被忽略?

5 个答案:

答案 0 :(得分:3)

这里的混淆是生成器表达式正在隐藏yield。这是函数形式:

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

执行.send()时,会执行内部yield x会产生x。然后表达式求值为.send的值,下一个收益率得出该值。这里有更清晰的形式:

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功能。 See here有一篇解释它的文章。它的结尾是它允许你产生在线程中运行的函数,一旦函数完成,twisted就会将函数的结果发送回代码。因此,您可以以非常线性和直观的方式编写严重依赖于线程的代码,而不必在整个地方编写大量的小函数。

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

答案 1 :(得分: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)

您的代码与此相同:

{{1}}

只有内部产量(“产量x”)在发电机中“使用” - 它用作外部产量的值。因此,这个生成器在产生范围的值之间来回迭代,并产生“发送”到那些产量的任何东西。如果你向内部收益发送一些东西,你就会收回它,但是如果你碰巧发送一个偶数迭代,那么发送将被发送到外部收益并被忽略。

答案 2 :(得分:2)

此生成器转换为:

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

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

答案 3 :(得分:0)

您编写的生成器等同于更详细:

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

正如您在此处所看到的,生成器表达式中隐含的第二个yield不会保存您传递的值,因此,根据生成器执行被阻止的位置send可能或者可能不起作用。

答案 4 :(得分:-1)

确实 - send方法适用于生成器对象,该生成器对象是您明确编写的协同例程的结果。在生成器表达式中很难得到一些意义 - 尽管它有效。

- 编辑 - 我以前写过这篇文章,但它是不完整的,因为生成器表达式中的产量可以跨实现预测 - 尽管在任何PEP中都没有提到。

  

生成器表达式并不意味着拥有yield关键字 - 我是   在这种情况下甚至没有定义行为。我们可以想一个   很少,并得到你的表达发生的事情,以满足   那些“无”来自哪里。但是,假设作为一方   如何在Python中实现产量的效果(可能是   甚至是依赖于实现的),而不是应该如此。

生成器表达式的正确形式,简化为:

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