【问题标题】:Creating iterators from a generator returns the same object从生成器创建迭代器返回相同的对象
【发布时间】:2018-08-25 19:47:17
【问题描述】:

假设我有一个庞大的数据列表,我想对其执行一些操作,并且我想让多个迭代器独立地执行此操作。

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) BUT "一旦tee() 进行了拆分,则不应在其他任何地方使用原始迭代;否则,迭代可能会在不通知 tee 对象的情况下前进。此迭代工具可能需要大量的辅助存储(取决于需要存储多少临时数据)。通常,如果一个迭代器在另一个迭代器启动之前使用了大部分或全部数据,则使用list() 而不是tee() 更快。 "
  • 更好:g1, g2 = ((e, 2*e) for e in data), ((e, 2*e) for e in data)(但这只有在data 是一个序列时才有效——它不能是一个迭代器。)

标签: python iterator generator


【解决方案1】:

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

return self

来自docs

迭代器对象本身需要支持以下 两种方法,共同构成迭代器协议:

iterator.__iter__() 返回迭代器对象本身。这是 需要允许容器和迭代器与 for 和 in 语句。这个方法对应的tp_iter slot Python/C API 中 Python 对象的类型结构。

iterator.__next__() 从容器中返回下一项。如果有 没有其他项目,引发 StopIteration 异常。这种方法 对应于 Python 的类型结构的 tp_iternext 槽 Python/C API 中的对象。

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

>>> 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) 会返回事物的 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)
    

    【讨论】:

    • 您根本没有解决OP的误解。我们所有人(包括 OP)都知道他们为两个迭代器使用相同的生成器。 OP 想知道的是为什么这会返回相同的迭代器两次。毕竟,如果您在列表、字符串、集合或任何其他数据结构上调用 iter 两次,您将得到 2 个不同的迭代器返回。那么为什么生成器不会发生这种情况呢?
    • 是的,我对生成器的理解是,它们描述了一种对数据序列进行一次转换的抽象方法,并且在调用 iter 时返回了该抽象的具体实例发电机。如果我正确理解您的回复,那么生成器是一个更有限的概念,每个生成器只能有一个唯一的迭代器?
    • 在我看来,生成器会返回它的 iter 方法而不是自身的副本,所以我没有详细说明。 javascript 的时间太长了,除非它让你的任务变得更加困难,否则没有任何东西会返回它自己的副本。
    • @JacobThalman 不,生成器是迭代器。它不返回自身的副本,因为它是一个迭代器,它的__iter__ 方法只返回self,它应该由迭代器协议指定。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    • 1970-01-01
    • 2019-06-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多