Python嵌套生成器

8 投票
2 回答
4500 浏览
提问于 2025-04-16 20:02

我在尝试在 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 个回答

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) 
8

你在做一些很疯狂的事情,简直是在重复造轮子。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 的值不会改变。

撰写回答