Python嵌套生成器

时间:2011-06-21 22:27:39

标签: python generator

我试图在Python 2.7.1上实现itertools.izip的反向功能。问题是我发现了一个问题,我没有解释。 解决方案1,iunzip_v1完美运行。但解决方案2. iunzip_v2,无法按预期工作。直到现在,我还没有找到关于这个问题的任何相关信息,并且阅读关于发电机的PEP,听起来应该有效,但事实并非如此。

import itertools
from operator import itemgetter

def iunzip_v1(iterable):
    _tmp, iterable = itertools.tee(iterable, 2)
    iters = itertools.tee(iterable, len(_tmp.next()))
    return tuple(itertools.imap(itemgetter(i), it) for i, it in enumerate(iters))

def iunzip_v2(iterable):
    _tmp, iterable = itertools.tee(iterable, 2)
    iters = itertools.tee(iterable, len(_tmp.next()))
    return tuple((elem[i] for elem in it) for i, it in enumerate(iters))

结果:

In [17]: l
Out[17]: [(0, 0, 0), (1, 2, 3), (2, 4, 6), (3, 6, 9), (4, 8, 12)]

In [18]: map(list, iunzip.iunzip_v1(l))
Out[18]: [[0, 1, 2, 3, 4], [0, 2, 4, 6, 8], [0, 3, 6, 9, 12]]

In [19]: map(list, iunzip.iunzip_v2(l))
Out[19]: [[0, 3, 6, 9, 12], [0, 3, 6, 9, 12], [0, 3, 6, 9, 12]]

似乎iunzip_v2正在使用最后一个值,因此生成器在第一个生成器内创建时不保留值。 我错过了什么,我不知道是什么。

如果事情可以让我澄清这种情况,请提前致谢。

更新 我在这里找到了解释PEP-289,我的第一次读物是在PEP-255。 我试图实现的解决方案是懒惰的,所以:

  zip(*iter) or izip(*...)

对我不起作用,因为* arg展开参数列表。

2 个答案:

答案 0 :(得分:7)

你正在以疯狂的方式重新发明轮子。 izip是它自己的反转:

>>> list(izip(*izip(range(10), range(10))))
[(0, 1, 2, 3, 4, 5, 6, 7, 8, 9), (0, 1, 2, 3, 4, 5, 6, 7, 8, 9)]

但这并没有完全回答你的问题,是吗?

嵌套生成器的问题是一个范围问题,因为在最外层的生成器已经运行之前,最内层的生成器不会被使用:

def iunzip_v2(iterable):
    _tmp, iterable = itertools.tee(iterable, 2)
    iters = itertools.tee(iterable, len(_tmp.next()))
    return tuple((elem[i] for elem in it) for i, it in enumerate(iters))

在这里,您生成三个生成器,每个生成器使用相同的变量i。没有制作此变量的副本。然后,tuple耗尽最外层的发生器,创建一个生成元组:

>>> iunzip_v2((range(3), range(3)))
(<generator object <genexpr> at 0x1004d4a50>, <generator object <genexpr> at 0x1004d4aa0>, <generator object <genexpr> at 0x1004d4af0>)

此时,这些生成器中的每一个都将为elem[i]的每个元素执行it。由于i现在对于所有三个生成器都等于3,因此每次都会获得最后一个元素。

第一个版本工作的原因是itemgetter(i)是一个闭包,它有自己的作用域 - 所以每次它返回一个函数时,它会生成一个新的作用域,其中i的值不会改变。

答案 1 :(得分:5)

好的,这有点棘手。当您使用类似i的名称时,它所代表的值仅在运行时查找。在这段代码中:

return tuple((elem[i] for elem in it) for i, it in enumerate(iters))

您返回了许多生成器(elem[i] for elem in it),并且每个生成器都使用相同的名称i。当函数返回时,tuple( .. for i in .. )中的循环已结束,i已设置为它的最终值(示例中为3)。将这些生成器评估为列表后,它们都会创建相同的值,因为它们使用相同的i

顺便说一下:

unzip = lambda zipped: zip(*zipped)