如何映射或嵌套Python 2.7函数生成器?

时间:2015-09-30 07:10:09

标签: python python-2.7 generator

如果我在Python 2.7中有一个非常简单(尽管可能非常复杂)的函数生成器,就像这样:

def accumulator():
    x = yield 0
    while True:
        x += yield x

可以使用哪个,如下:

>>> a = accumulator()
>>> a.send(None)
0
>>> a.send(1)
1
>>> a.send(2)
3
>>> a.send(3)
6

除了乘以2之外,另一个产生相同结果的函数发生器的简单包装器是什么?上面的函数生成器很简单,但请假设复制粘​​贴太复杂。我正在尝试一些事情,比如:

def doubler():
    a = accumulator()
    a.send(None)
    y = yield 0
    while True:
        y = 2 * a.send(yield y)

或者,想象一些更简单的事情:

def doubler():
    a = accumulator()
    a.send = lambda v: 2 * super(self).send(v)
    return a

这两个都非常糟糕,所以我不会分享语法错误,但它可能会说明我正在尝试做什么。

理想情况下,我想得到一些东西,比如:

>>> d = doubler()
>>> d.send(None)
0
>>> d.send(1)
2
>>> d.send(2)
6
>>> d.send(3)
12

结果与原始结果完全相同,但加倍。

我试图避免重复一个非常复杂的函数生成器来创建相同的结果,除了按已知因子缩放。

第二个生成器最终会有一个不同的输入流,所以我不能只使用第一个生成器的结果并将其加倍。我需要第二个独立的发生器,包装第一个。

输入流是不确定的,因此无法生成整个序列然后进行转换。

似乎我想映射或嵌套这些函数生成器,但我不确定是否适当的术语,所以我在Google中无处可去。

4 个答案:

答案 0 :(得分:2)

我没有尝试过这个,但是有一些东西:

class Doubler:
  def __init__(self, g):
    self.g = g()

  def __next__(self):
    return self.send(None)

  def send(self, val):
    return self.g.send(val)*2

此外,在Python 3.5之后,从collections.abc.Container扩展此内容将消除__next__的需要,也会使这成为一个合适的生成器(它目前不支持__throw__等,但它们只是样板。)

编辑:是的,这有效:

In [1]: %paste
def accumulator():
    x = yield 0
    while True:
        x += yield x

## -- End pasted text --

In [2]: %paste
class Doubler:
    def __init__(self, g):
        self.g = g()
    def __next__(self):
        return self.send(None)
    def send(self, val):
        return self.g.send(val)*2

## -- End pasted text --

In [3]: d = Doubler(accumulator)

In [4]: d.send(None)
Out[4]: 0

In [5]: d.send(1)
Out[5]: 2

In [6]: d.send(2)
Out[6]: 6

In [7]: d.send(3)
Out[7]: 12

答案 1 :(得分:2)

您只需将yield移到y传递给a的表达式之外:

def doubler():
    a = accumulator()
    next(a)
    y = yield 0
    while True:
        y = yield (2 * a.send(y))

然后:

>>> a = accumulator()
... d = doubler()
... next(a)
... next(d)
... for i in range(10):
...     print(a.send(i), d.send(i))
0 0
1 2
3 6
6 12
10 20
15 30
21 42
28 56
36 72
45 90

答案 2 :(得分:2)

如果您需要与协程具有相同的接口(即使用send方法),那么BrenBarn的解决方案可能很简单。*

如果你的界面略有不同,那么更高阶的功能就更简单了:

def factor_wrapper(coroutine, factor):
    next(coroutine)
    return lambda x, c=coroutine, f=factor: f * c.send(x)

您可以按如下方式使用它:

>>> a = accumulator()
>>> a2 = factor_wrapper(a, 2)
>>> print a2(1)
2
>>> print a2(2)
6
>>> print a2(3)
12

*实际上你可以剃掉几行来使它总共4行,但并没有真正降低复杂性。

def doubler(a):
    y = yield next(a)
    while True:
        y = yield (2 * a.send(y))

甚至更短......

def doubler(a, y=None):
    while True:
        y = yield 2 * a.send(y)

上述任何一种都可以使用如下:

>>> a = accumulator()
>>> a2 = doubler(a)
>>> print a2.send(None) # Alternatively next(a2)
0
>>> print a2.send(1)
2
>>> print a2.send(2)
6
>>> print a2.send(3)
12

答案 3 :(得分:0)

我认为这就是你想要的:

def doubler():
    a = accumulator()
    y = a.send(None)
    x = yield 0
    while True:
        y = a.send(x)
        x = yield 2 * y

这完全包含了accumulator实现,但您也可以将其设为可见,并将其作为参数a传递给doubler。