9

如果我创建两个列表并压缩它们

a=[1,2,3]
b=[7,8,9]
z=zip(a,b)

然后我将 z 类型转换为两个列表

l1=list(z)
l2=list(z)

然后 l1 的内容变成了 [(1,7),(2,8),(3,9)],但 l2 的内容只是 []。

我想这是 python 在迭代方面的一般行为。但是作为一个从 C 家族迁移出来的新手程序员,这对我来说没有意义。为什么它会以这种方式表现?有没有办法解决这个问题?

我的意思是,是的,在这个特定的例子中,我可以将 l1 复制到 l2 中,但一般来说,有没有办法在我迭代一次之后“重置”Python 用来迭代“z”的任何东西?

4

5 回答 5

12

没有办法“重置”发电机。但是,您可以使用itertools.tee“复制”迭代器。

>>> z = zip(a, b)
>>> zip1, zip2 = itertools.tee(z)
>>> list(zip1)
[(1, 7), (2, 8), (3, 9)]
>>> list(zip2)
[(1, 7), (2, 8), (3, 9)]

这涉及缓存值,因此仅当您以大致相同的速率迭代两个可迭代对象时才有意义。(换句话说,不要像我这里那样使用它!)

另一种方法是传递生成器函数,并在您想要迭代它时调用它。

def gen(x):
    for i in range(x):
        yield i ** 2

def make_two_lists(gen):
    return list(gen()), list(gen())

但是现在您必须在传递参数时将参数绑定到生成器函数。你可以使用lambda它,但很多人觉得lambda丑陋。(但不是我!YMMV。)

>>> make_two_lists(lambda: gen(10))
([0, 1, 4, 9, 16, 25, 36, 49, 64, 81], [0, 1, 4, 9, 16, 25, 36, 49, 64, 81])

我希望不用说,在大多数情况下,最好只是列出并复制它。

此外,作为解释这种行为的更一般的方式,请考虑这一点。生成器的目的是产生一系列值,同时在迭代之间保持某种状态。现在,有时,您可能想要执行以下操作,而不是简单地迭代生成器:

z = zip(a, b)
while some_condition():
    fst = next(z, None)
    snd = next(z, None)
    do_some_things(fst, snd)
    if fst is None and snd is None:
        do_some_other_things()

假设这个循环可能会可能不会用尽z。现在我们有一个处于不确定状态的生成器!因此,在这一点上,以明确定义的方式限制生成器的行为是很重要的。尽管我们不知道生成器在其输出中的位置,但我们知道 a) 所有后续访问都将在系列中生成后面的值,并且 b) 一旦它为“空”,我们就准确地获得了系列中的所有项目一次。我们操纵 的状态的能力越强z,推理它的难度就越大,所以我们最好避免违反这两个承诺的情况。

当然,正如 Joel Cornett 在下面指出的那样,可以编写一个通过该send方法接受消息的生成器;并且可以编写一个可以使用send. 但请注意,在这种情况下,我们所能做的就是发送一条消息。我们不能直接操纵生成器的状态,因此对生成器状态的所有更改都是明确定义的(由生成器本身 - 假设它是正确编写的!)。send真的是为了实现coroutines,所以我不会为此目的使用它。日常生成器几乎从不使用发送给它们的值做任何事情——我认为这正是我上面给出的原因。

于 2012-06-02T21:41:04.010 回答
3

如果您需要两个列表副本,如果您需要修改它们,那么我建议您制作一次列表,然后复制它:

a=[1,2,3]
b=[7,8,9]
l1 = list(zip(a,b))
l2 = l1[:]
于 2012-06-03T07:13:05.357 回答
2

只需使用一次从迭代器中创建一个列表list(),然后再使用它。

只是碰巧zip返回了一个生成器,它是一个只能迭代一次的迭代器。

您可以根据需要多次迭代列表。

于 2012-06-02T21:39:23.230 回答
1

不,没有办法“重置它们”。

生成器根据需要一次一个地生成它们的输出,然后在输出耗尽时完成。

把它们想象成读取一个文件,一旦你读完,如果你想再次读取数据,就必须重新启动。

如果您需要保留生成器的输出,请考虑将其存储在例如列表中,然后根据需要经常重复使用它。(有点类似于指导使用的决定xrange(),一个生成器 vsrange()在 v2 中创建了一个完整的内存项目列表)

更新:更正的术语,暂时的大脑中断......

于 2012-06-02T21:38:21.630 回答
0

还有另一种解释。作为程序员,您可能了解类与实例(即对象)之间的区别。据说zip()是一个内置函数(在官方文档中)。实际上,它是一个内置的生成器函数。这意味着它更像是类。您甚至可以在交互模式下尝试:

>>> zip
<class 'zip'>

类是类型。因此,以下内容也应该清楚:

>>> type(zip)
<class 'type'>

z是类的实例,你可以考虑zip()调用类的构造函数:

>>> a = [1, 2, 3]
>>> b = [7, 8, 9]
>>> z = zip(a, b)
>>> z
<zip object at 0x0000000002342AC8>
>>> type(z)
<class 'zip'>

Thez是一个迭代器(对象),它保留在 and 的迭代器ab。由于它的通用实现,z(或zip类)没有办法通过aorb或任何序列重置迭代器。正因为如此,没有办法重置z. 解决您的具体问题的最干净的方法是复制列表(正如您在问题中提到的和Lennart Regebro 建议的那样)。另一种可以理解的方法是使用zip(a, b)两次,从而构造两个z类似的迭代器,它们从一开始就以相同的方式运行:

>>> lst1 = list(zip(a, b))
>>> lst2 = list(zip(a, b))

但是,这通常不能用于相同的结果。考虑ab成为基于某些当前条件(例如从多个温度计读取的温度)生成的独特序列。

于 2012-06-03T09:15:58.317 回答