Python嵌套生成器
我在尝试在 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 个回答
好吧,这个有点复杂。当你使用像 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)
你在做一些很疯狂的事情,简直是在重复造轮子。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>)
到这个时候,这些生成器会对 it
的每个元素执行 elem[i]
。而因为此时 i
对于所有三个生成器来说都是 3,所以每次你得到的都是最后一个元素。
第一个版本之所以能工作,是因为 itemgetter(i)
是一个闭包,它有自己的作用域——所以每次它返回一个函数时,都会生成一个新的作用域,在这个作用域内 i
的值不会改变。