Короткий ответ
Генератор вычисляет и отдаёт элементы по одному лениво, не храня всю последовательность в памяти, тогда как список сразу материализует все элементы; генератор предпочтителен, когда данные обрабатываются последовательно и целиком в памяти не нужны.
Как это работает подробнее
Generator expression или функция с yield создают итератор, который на каждом шаге вычисляет следующее значение и сохраняет только текущее состояние, а не весь набор данных.
Пример кода
example.py
# Список: все 10**8 значений материализуются в памяти сразу
total_list = sum([x * x for x in range(10**8)])
# Генератор: значения вычисляются и отбрасываются по одному
total_gen = sum(x * x for x in range(10**8))
# Оба варианта дают одинаковый результат, но первый расходует
# на порядки больше памяти — проверяется через tracemalloc или resource.getrusageЧто использовать на практике
- Практика. При построчной обработке большого файла или потоковой выдаче результатов из API генератор позволяет не загружать весь датасет в память; но если нужно несколько проходов по данным или индексированный доступ, список подходит лучше.
Подводные камни
- Риск. Используют list comprehension там, где данные читаются один раз и сразу суммируются/фильтруются, из-за чего программа упирается в память на больших объёмах; пытаются повторно итерироваться по уже исчерпанному генератору, получая пустой результат без ошибки.