Создание итераторов из генератора возвращает тот же объект - PullRequest
0 голосов
/ 25 августа 2018

Допустим, у меня есть большой список данных, с которыми я хочу выполнить некоторую операцию, и я хотел бы, чтобы несколько итераторов выполняли эту операцию независимо.

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 тоже увеличивается, а это не то поведение, которое мне нужно.

Что здесь происходит, и есть ли способ сделать то, что я пытаюсь сделать?Я использую Python 2.7 в Ubuntu 14.04.

Редактировать:

Я также опробовал следующее:

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 ... Очевидно, генераторы - это просто более ограниченная концепция, которую я себе представлял.

Ответы [ 2 ]

0 голосов
/ 25 августа 2018

Генераторы являются итераторами . Все итераторы с хорошим поведением имеют метод __iter__, который должен просто

return self

Из документов

Сами объекты итератора должны поддерживать следующее два метода, которые вместе образуют протокол итератора:

iterator.__iter__() Возвращает сам объект итератора . Это требуется, чтобы и контейнеры, и итераторы могли использоваться с для и в заявлениях. Этот метод соответствует слоту tp_iter структура типов для объектов Python в API Python / C.

iterator.__next__() Вернуть следующий предмет из контейнера. Если там больше нет предметов, вызовите исключение StopItate. Этот метод соответствует слоту tp_iternext структуры типа для Python объекты в API Python / C.

Итак, рассмотрим другой пример итератора:

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

В этом случае it1 и it2 будут двумя отдельными объектами итератора.

0 голосов
/ 25 августа 2018

Вы используете один и тот же генератор для обоих iters. Вызов iter(thing) возвращает iter объекта, если он у него есть, поэтому iter (генератор) возвращает одно и то же при обоих вызовах. 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)
...