Генератор — одноразовый итератор: почему второй list() пуст

Генератор — одноразовый итератор: почему второй list() пуст

@python_quiz

Разберем этот квиз

Код, о котором идёт речь:

python

Кратко — первый вызов list(c) напечатает [0, 1, 2], второй — пустой список. Ниже объясню подробно почему и разберу варианты ответов.

Что делает этот код — пошагово

  1. c = (i for i in range(3)) — создаётся генератор (generator expression). Генератор реализует протокол итератора: у него есть состояние и он выдаёт элементы по запросу.
  2. list(c) — начинает итерировать генератор c, получает элементы 0, 1, 2 и собирает их в список. Генератор при этом полностью исчерпывается.
  3. Второй list(c) — пытается итерировать тот же самый объект-генератор, но он уже в состоянии «исчерпан» (больше элементов нет), поэтому возвращается пустой список.

Почему генератор одноразовый

  • Генератор — это итератор. Итераторы сохраняют текущее состояние обхода.
  • После прохода по всем элементам итератор считается завершённым; последующие попытки получить элементы возвращают StopIteration, поэтому коллекции, построенные из такого итератора, пусты.

Анализ вариантов ответов (логика)

  • Вариант, где оба вызова дают полный список, предполагает, что c — не итератор, а «многократный» iterable (например, список). Это неверно для генератора.
  • Вариант, где первый пуст, а второй — полный, противоречит порядку выполнения: первый вызов как раз потребляет элементы.
  • Вариант, где оба пусты, означал бы, что генератор был пуст изначально — но он генерирует 3 значения.
  • Правильный вывод программы: сначала список из трёх элементов, затем пустой список.

Итого: вывод будет

text

Как получить повторяемый результат — варианты решения

Если требуется несколько раз получить одинаковые списки, можно:

  1. Сохранить значения в список один раз:
python
  1. Использовать list comprehension сразу (это создаёт список, а не генератор):
python
  1. Создавать новый генератор при каждом обходе:
python
  1. Если нужно «разветвить» итератор на несколько независимых итераторов — использовать itertools.tee (учтите предполагаемую память для буферизации):
python

Дополнительные замечания

  • range(3) сам по себе — итерируемый объект, но не итератор (вызов iter(range(3)) вернёт новый итератор). Генератор же сразу возвращает итератор.
  • Если генератор большой или вычисление значений дорогое, материализация в список может быть нежелательной с точки зрения памяти — в таких случаях планируйте, как и где вы будете потреблять элементы.

Вывод

Генераторы — удобный инструмент для ленивой генерации значений, но важно помнить: они одноразовы. Если нужно повторно пройти по результатам — материализуйте их в контейнер или создавайте новый генератор для каждого обхода.

Report Page