Почему я не могу выполнить итерацию дважды по одному и тому же итератору? Как я могу "сбросить" итератор или повторно использовать данные?
Рассмотрим код:
def test(data):
for row in data:
print("first loop")
for row in data:
print("second loop")
Когда data
есть итератор, например, итератор списка или выражение генератора *, это не работает:
>>> test(iter([1, 2]))
first loop
first loop
>>> test((_ for _ in [1, 2]))
first loop
first loop
Это печатается first loop
несколько раз, поскольку data
непустое значение. Однако оно не печатается second loop
. Почему повторное выполнение data
работает в первый раз, но не во второй? Как я могу заставить это работать во второй раз?
Помимо for
циклов, похоже, та же проблема возникает при любом типе итерации: понимании списка / набора / dict, передаче итератора в list()
, sum()
или reduce()
и т.д.
С другой стороны, если data
есть другой вид итерируемости, такой как list
или a range
(которые оба являются последовательностями), оба цикла выполняются как ожидалось:
>>> test([1, 2])
first loop
first loop
second loop
second loop
>>> test(range(2))
first loop
first loop
second loop
second loop
* Еще примеры:
- файловые объекты
- генераторы, созданные на основе явной функции генератора
filter
,map
, иzip
объекты (в 3.x)enumerate
Объектыcsv.reader
s- различные итераторы, определенные в
itertools
стандартной библиотеке
Для объяснения общей теории и терминологии см. Что такое iterator, iterable и повторение?.
Чтобы определить, является ли ввод итератором или итерацией "многократного использования", см. раздел Убедитесь, что аргумент может быть повторен дважды.
Переведено автоматически
Ответ 1
Итератор может быть использован только один раз. Например:
lst = [1, 2, 3]
it = iter(lst)
next(it)
# => 1
next(it)
# => 2
next(it)
# => 3
next(it)
# => StopIteration
Когда итератор передается в for
цикл вместо этого, этот последний StopIteration
приведет к завершению работы в первый раз. Попытка использовать тот же итератор в другом цикле for немедленно вызовет StopIteration
повторение, потому что итератор уже был использован.
Простой способ обойти это - сохранить все элементы в списке, который можно просматривать столько раз, сколько необходимо. Например:
data = list(data)
Однако, если итератор будет выполнять итерации по многим элементам, лучше создать независимые итераторы, используя tee()
:
import itertools
it1, it2 = itertools.tee(data, 2) # create as many as needed
Теперь каждую итерацию можно выполнять по очереди:
for e in it1:
print("first loop")
for e in it2:
print("second loop")
Ответ 2
Итераторы (например, из вызова iter
, из выражений генератора или из функций генератора, которые yield
) отслеживают состояние и могут быть использованы только один раз.
Это объясняется в ответе Оскара Лопеса, однако рекомендация этого ответа использовать itertools.tee(data)
вместо list(data)
из соображений производительности вводит в заблуждение. В большинстве случаев, когда вы хотите выполнить итерацию по всему data
, а затем выполнить итерацию по всему этому снова, tee
занимает больше времени и использует больше памяти, чем простое преобразование всего итератора в список, а затем повторное выполнение по нему дважды. Согласно документации:
Этот инструмент itertool может потребовать значительного дополнительного хранилища (в зависимости от того, сколько временных данных необходимо сохранить). В общем, если один итератор использует большую часть или все данные до запуска другого итератора, быстрее использовать
list()
вместоtee()
.
tee
может быть предпочтительнее, если вы будете использовать только первые несколько элементов каждого итератора, или если вы будете чередовать использование нескольких элементов из одного итератора, а затем нескольких из другого.
Ответ 3
Как только итератор будет исчерпан, он больше ничего не даст.
>>> it = iter([3, 1, 2])
>>> for x in it: print(x)
...
3
1
2
>>> for x in it: print(x)
...
>>>
Ответ 4
Как мне дважды выполнить цикл по итератору?
Обычно это невозможно. (Объяснено позже.) Вместо этого выполните одно из следующих действий:
Собрать итератор во что-то, что можно повторять несколько раз.
items = list(iterator)
for item in items:
...Недостаток: это требует затрат памяти.
Создайте новый итератор. Обычно создание нового итератора занимает всего микросекунду.
for item in create_iterator():
...
for item in create_iterator():
...Недостаток: сама итерация может быть дорогостоящей (например, чтение с диска или сети).
Сбросить "итератор". Например, с файловыми итераторами:
with open(...) as f:
for item in f:
...
f.seek(0)
for item in f:
...Недостаток: большинство итераторов невозможно "сбросить".
Философия Iterator
Обычно, хотя и не технически1:
- Iterable: Зацикливаемый объект, представляющий данные. Примеры:
list
,tuple
,str
. - Итератор: указатель на некоторый элемент итерируемого объекта.
Если бы мы определили итератор последовательности, это могло бы выглядеть примерно так:
class SequenceIterator:
index: int
items: Sequence # Sequences can be randomly indexed via items[index].
def __next__(self):
"""Increment index, and return the latest item."""
Здесь важно то, что обычно итератор не хранит внутри себя никаких реальных данных.
Итераторы обычно моделируют временный "поток" данных. Этот источник данных используется в процессе итерации. Это хороший намек на то, почему нельзя выполнять цикл по произвольному источнику данных более одного раза. Для этого нам нужно открыть новый временный поток данных (т. Е. Создать новый итератор).
Исчерпывающий Iterator
Что происходит, когда мы извлекаем элементы из итератора, начиная с текущего элемента итератора и продолжая до тех пор, пока он полностью не будет исчерпан? Это то, что делает for
цикл:
iterable = "ABC"
iterator = iter(iterable)
for item in iterator:
print(item)
Давайте поддержим эту функциональность в SequenceIterator
, сообщив for
циклу, как извлечь next
элемент:
class SequenceIterator:
def __next__(self):
item = self.items[self.index]
self.index += 1
return item
Подождите. Что, если index
пройдет мимо последнего элемента items
? Мы должны создать для этого безопасное исключение:
class SequenceIterator:
def __next__(self):
try:
item = self.items[self.index]
except IndexError:
raise StopIteration # Safely says, "no more items in iterator!"
self.index += 1
return item
Теперь цикл for знает, когда прекратить извлечение элементов из итератора.
Что произойдет, если мы сейчас попытаемся повторить цикл по итератору снова?
iterable = "ABC"
iterator = iter(iterable)
# iterator.index == 0
for item in iterator:
print(item)
# iterator.index == 3
for item in iterator:
print(item)
# iterator.index == 3
Поскольку второй цикл начинается с текущего iterator.index
, который равен 3, ему больше нечего печатать, и поэтому iterator.__next__
вызывает StopIteration
исключение, в результате чего цикл немедленно завершается.
1 Технически:
- Iterable: объект, который возвращает итератор при
__iter__
вызове на нем. - Итератор: объект, который можно многократно вызывать
__next__
в цикле для извлечения элементов. Кроме того, вызов__iter__
для него должен возвращать егоself
.
Подробнее здесь.