Сравните списковое включение и генераторное выражение при обработке большого потока: какое ключевое различие определяет пиковое потребление памяти?
Списковое включение сразу создаёт и хранит весь результат, поэтому его дополнительное потребление памяти растёт вместе с числом элементов. Генераторное выражение вычисляет элементы по одному при обходе и обычно хранит только состояние обхода и текущий результат.
Поэтому для большого потока генераторное выражение снижает пиковое потребление памяти, если потребитель тоже обрабатывает данные потоково. Это не означает, что генератор делает память независимой от размера источника: уже загруженный список или операция, собирающая результат в список, сохранят соответствующие данные.
Ленивые вычисления появились как способ обрабатывать последовательности, не создавая промежуточные коллекции целиком. Такой подход особенно важен для больших файлов, сетевых потоков и потенциально бесконечных последовательностей.
Списковые включения сохранили удобство краткой записи построения коллекции. Генераторные выражения предоставили похожий синтаксис, но с отложенным вычислением и передачей элементов потребителю по мере необходимости.
При преобразовании большого набора данных промежуточный результат может занимать значительный объём памяти. Если создать список, память потребуется сразу для всех результирующих объектов; при нескольких последовательных преобразованиях могут одновременно существовать несколько таких промежуточных коллекций.
Неверный выбор приводит либо к избыточному потреблению памяти, либо к неожиданным ограничениям генератора. Например, генератор нельзя повторно обойти после исчерпания без создания нового генератора, а некоторые операции сами требуют полной материализации результата.
Списковое включение выполняет цикл немедленно: для каждого элемента источника вычисляется выражение, и результат добавляется в новый список. Память под результирующую коллекцию выделяется по мере роста списка, поэтому для N результатов обычно требуется память порядка O(N) плюс память источника и временных объектов.
Генераторное выражение создаёт объект-генератор. При каждом запросе следующего элемента он возобновляет выполнение, получает очередной элемент источника, вычисляет результат и приостанавливается. Сам результат предыдущего шага не хранится генератором после передачи потребителю, если на него больше нет ссылок.
В первом случае все квадраты остаются в списке. Во втором случае sum последовательно запрашивает значения и не требует отдельного списка квадратов. При этом range сам по себе не хранит миллионы чисел, а если источником был бы заранее созданный список, его память всё равно уже была бы занята.
Генератор экономит память только при ленивой цепочке обработки. Вызов list(generator), сортировка или иная операция, требующая всех элементов, снова материализует данные. Списковое включение иногда быстрее при повторном использовании результата или когда размер данных мал: список уже готов, поддерживает индексацию и повторный обход.
Сервис читает большой журнал, отбирает записи определённого типа и вычисляет по ним статистику. Вариант со списковым включением прост и позволяет повторно обходить отфильтрованные записи, но требует памяти под весь промежуточный результат; при параллельных запросах это может привести к резкому росту потребления памяти.
Вариант с генераторным выражением передаёт записи непосредственно статистическому агрегатору. Он использует мало дополнительной памяти, но результат нельзя повторно обойти, а ошибки чтения могут проявляться только во время фактического потребления элементов.
Выбирается генераторная обработка, потому что данные используются один раз, а ограничение памяти важнее возможности индексации и повторного обхода. Если же фильтрованный набор нужен нескольким независимым этапам, разумнее один раз материализовать его в список либо сохранить в отдельное промежуточное хранилище.
Нет. Оно не хранит весь результирующий набор, но удерживает ссылку на источник и своё внутреннее состояние. Если источник уже является большим списком, его память никуда не исчезает; кроме того, отдельный элемент может быть крупным, а потребитель может временно накапливать полученные значения.
list или sorted?Потребитель запросит все элементы и сохранит их. Генератор по-прежнему будет вычислять значения лениво относительно момента создания, но итоговая операция материализует весь результат, поэтому преимущество по пиковому потреблению памяти в основном исчезнет.
Список оправдан, если результат нужно многократно обходить, индексировать, измерять его длину или использовать независимыми этапами без повторного выполнения исходных вычислений. Повторный обход генератора невозможен после исчерпания, а создание нового может повторить дорогой ввод или вычисления; компромисс состоит в дополнительной памяти ради повторного использования и предсказуемого доступа.