Вопрос проверяет понимание ленивых вычислений, итераторов и того, как выбор структуры данных влияет на потребление памяти.
Список создаётся сразу целиком и занимает память под все элементы. Генератор вычисляет следующий элемент только когда его запрашивают. Поэтому память нужна только для текущего состояния генератора, а не для всего набора данных. Это особенно полезно при обработке больших потоков данных или файлов.
Генератор — это итератор, который вычисляет значения лениво и сохраняет только своё текущее состояние (позицию выполнения и локальные переменные).
Если вы строите список, Python:
Вычисляет все элементы
Выделяет память под структуру списка
Хранит ссылки на каждый элемент
Пример:
numbers = [i * i for i in range(10_000_000)]
Здесь в памяти окажется весь список (пусть даже вам нужно первые 10 значений).
Генератор:
Не вычисляет элементы заранее
Хранит только состояние выполнения (где остановился)
Возвращает элементы по запросу next()
numbers = (i * i for i in range(10_000_000))
first = next(numbers)
second = next(numbers)
Потому что генератору не нужно держать 10 миллионов значений — он держит:
текущий i
контекст выполнения
ссылки на используемые объекты
Плохой вариант для больших файлов:
lines = open("big.log").read().splitlines()
Лучше:
with open("big.log") as f:
for line in f:
# обработка строки
...
Здесь строки читаются и обрабатываются по мере прохода, не загружая файл целиком.
Экономия памяти обычно означает:
Нельзя быстро получить длину последовательности
Нельзя «перемотать назад» без пересоздания генератора
Если нужен многократный проход по данным, иногда выгоднее материализовать список
Генераторы экономят память за счёт ленивых вычислений: данные не хранятся целиком, а выдаются по мере запроса. Это подходит для больших объёмов данных и потоковой обработки.