从一个生成器创建迭代器会返回同一个对象。

4
假设我有一个大型数据列表,我想对其执行某些操作,并且我希望有多个迭代器独立执行此操作。
data = [1,2,3,4,5]
generator = ((e, 2*e) for e in data)
it1 = iter(generator)
it2 = iter(generator)

我预期这些迭代器应该是不同的代码对象,但是it1 is it2 返回了 True... 更加令人困惑的是,对于以下的生成器也是如此:
# copied data
gen = ((e, 2*e) for e in copy.deepcopy(data))
# temp object
gen = ((e, 2*e) for e in [1,2,3,4,5])

实际上这意味着当我调用 next(it1) 时,it2也会被增加,而这不是我想要的行为。

这里发生了什么?有没有办法做到我想做的事情?我在Ubuntu 14.04上使用Python 2.7。

编辑:

我还尝试了以下代码:

gen = (e for e in [1,2,3,4,5])
it = iter(gen)
next(it)
next(it)
for e in gen:
    print e

打印出3 4 5...看来生成器只是一个比我想象中更受限的概念。


it1, it2 = itertools.tee(generator)的意思是:将generator生成器拆分成两个迭代器it1it2。但需要注意,一旦使用tee()进行拆分后,原始可迭代对象不应在其他地方使用,否则可迭代对象可能会被提前进行遍历,而不通知tee对象。此迭代工具可能需要大量辅助存储(取决于需要存储多少临时数据)。一般来说,如果一个迭代器在另一个迭代器开始之前使用了大部分或全部数据,则使用list()而不是tee()更快。 - Steven Rumbalski
更好的写法是:g1, g2 = ((e, 2*e) for e in data), ((e, 2*e) for e in data)(但这只适用于 data 是一个序列,不能是迭代器)。 - Steven Rumbalski
2个回答

5
生成器是迭代器。所有表现良好的迭代器都有一个__iter__方法,该方法应该只是简单地。
return self

文档中得知:

迭代器对象必须支持以下两个方法,它们一起形成了迭代器协议:

iterator.__iter__() 返回迭代器对象本身。这是为了允许容器和迭代器与for和in语句一起使用。此方法对应于Python/C API中Python对象的类型结构中的tp_iter插槽。

iterator.__next__() 返回容器中的下一个项。如果没有更多项,则引发StopIteration异常。此方法对应于Python/C API中Python对象的类型结构中的tp_iternext插槽。

因此,考虑另一个迭代器的示例:

>>> x = [1, 2, 3, 4, 5]
>>> it = iter(x)
>>> it2 = iter(it)
>>> next(it)
1
>>> next(it2)
2
>>> it is it2
True

因此,列表是可迭代的,因为它有一个返回迭代器的__iter__方法。此迭代器也有一个__iter__方法,它应该总是返回自身,但它还有一个__next__方法。
考虑以下示例:
>>> x = [1, 2, 3, 4, 5]
>>> it = iter(x)
>>> hasattr(x, '__iter__')
True
>>> hasattr(x, '__next__')
False
>>> hasattr(it, '__iter__')
True
>>> hasattr(it, '__next__')
True
>>> next(it)
1
>>> next(x)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'list' object is not an iterator

而对于一个生成器:

>>> g = (x**2 for x in range(10))
>>> g
<generator object <genexpr> at 0x104104390>
>>> hasattr(g, '__iter__')
True
>>> hasattr(g, '__next__')
True
>>> next(g)
0

现在,你正在使用生成器表达式。但你也可以使用生成器函数。实现你想要的最直接的方法就是使用:

def paired(data):
    for e in data:
        yield (e, 2*e)

然后使用:

it1 = paired(data)
it2 = paired(data)

在这种情况下,it1it2将是两个独立的迭代器对象。

2
你正在同时使用相同的生成器。调用iter(thing)如果thing有,将返回thing的iter,所以,iter(generator)两次调用将返回相同的结果。详情请参考:https://docs.python.org/3/library/stdtypes.html#generator-types
data = [1,2,3,4,5]
generator = ((e, 2*e) for e in data)
it1 = iter(generator)
it2 = iter(generator)

type(it1)
generator

以下是两种获取唯一生成器的方法:
import itertools
data = [1,2,3,4,5]
generator = ((e, 2*e) for e in data)
it1, it2 = itertools.tee(generator)
type(it1)
itertools._tee

或者:

data = [1,2,3,4,5]
it1 = ((e, 2*e) for e in data)
it2 = ((e, 2*e) for e in data)
type(it1)
generator

两种解决方案都能产生这个结果:
next(it1)
(1, 2)
next(it2)
(1, 2)

1
你根本没有解决原帖作者的误解。我们都(包括原帖作者)知道他们在使用相同的生成器来迭代。原帖作者想知道的是为什么这会返回两次相同的迭代器。毕竟,如果你在列表、字符串、集合或任何其他数据结构上调用iter两次,你将得到两个不同的迭代器。那么为什么生成器不是这样的呢? - Aran-Fey
是的,我的理解是生成器描述了一种抽象的方式来逐个对数据序列执行转换,当在生成器上调用iter时,会返回该抽象的具体实例。如果我正确理解了您的回复,那么生成器是一个更为有限的概念,每个生成器只能有一个唯一的迭代器? - Jacob Thalman
我认为生成器应该返回其iter方法而不是自身的副本,这对我来说很直观,所以我没有详细说明。在javascript中花费了太多时间,除非它使您的任务更加困难,否则没有任何东西会返回自身的副本。 - keithpjolley
@JacobThalman 不,生成器是迭代器。它不会返回自身的副本,因为它是一个迭代器,它的__iter__方法只是简单地返回self,正如迭代器协议所规定的那样。 - juanpa.arrivillaga

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接