The AI / news

Итераторы и генераторы : как обрабатывать данные, потребляя меньше памяти

Итераторы и генераторы : как обрабатывать данные, потребляя меньше памяти
3 мин чтения

Скрипт, предназначенный для суммирования журнала платежей, работал без сбоев год, прежде чем завершился ошибкой на слишком большом файле. В руководстве, посвящённом итераторам и генераторам Python, показано, как строить цепочки обработки, которые читают данные по одной записи за раз, не загружая их все в память.

В описанном случае скрипт открывал файл, создавал список проанализированных записей, фильтровал ошибки, а затем выводил таблицу. После изменения в ротации журналов недельный трафик оказался в одном файле размером 9 ГБ. Ночная обработка прервалась с MemoryError, не успев выдать ни одного сводного отчёта. По словам автора, логика анализа была не при чём: проблема заключалась в списке, который вынуждает хранить все записи одновременно.

Работа основана на простом контракте. Итерируемый объект, такой как список, строка или открытый файл, может предоставить итератор, то есть курсор, который продвигается элемент за элементом и никогда не возвращается назад. Цикл for вызывает эти шаги автоматически, а конец последовательности сигнализируется исключением StopIteration, которое не является ошибкой. Исчерпанный итератор не сбрасывается: функция, которая возвращает « ничего » при повторном чтении, не обязательно пуста — она просто уже была потреблена.

В руководстве рекомендуется начать с объекта файла, который уже является итератором. Чтение файла с помощью f.readlines() создаёт список всех строк, тогда как for line in f читает только один буферизованный блок за раз. Функции-генераторы, использующие ключевое слово yield, затем позволяют разбить анализ на именованные этапы. Инструкция yield from объединяет несколько файлов ротации в единый поток, а генераторные выражения, записанные в круглых скобках, служат для небольших промежуточных преобразований. Автор указывает на ловушку: самая левая часть генераторного выражения вычисляется немедленно.

Измерять, прежде чем доверять

В руководстве также подчёркиваются моменты, где список возвращается незаметно. Вызовы list(), sorted(), словарь, группирующий записи по идентификатору запроса, или создание DataFrame возвращают весь поток в память целиком. Генераторы также не быстрее списков: их преимущество — ограниченное потребление памяти и возможность перестановки этапов. Для пакетной обработки Python 3.12 предлагает itertools.batched(), а для более ранних версий приведён эквивалентный рецепт на основе islice.

Чтобы проверить реальное поведение, автор рекомендует модуль tracemalloc, который измеряет память, выделяемую Python, в текущем значении и по пику. На синтетическом журнале из 200 000 строк, объёмом около 17 МБ, « списочная » версия достигает пика в 198 МБ на ноутбуке, против 0,4 МБ для потоковой версии. Автор уточняет, что это результаты однократного выполнения, а не бенчмарк. Он также напоминает, что tracemalloc не видит выделений памяти расширениями на C и кэша системы.

Ограничения потокового подхода

Некоторые операции не могут быть выполнены в потоковом режиме. Глобальная сортировка, точное удаление дубликатов по всему файлу или вычисление процентиля по всем значениям задержки требуют знания всех записей. В этом случае руководство упоминает приближённый алгоритм на основе выборки или внешний инструмент для сортировки на диске.

В руководстве также отмечается, что этот подход остаётся в рамках чистого Python. Pandas, Spark, Dask или асинхронные потоки решают схожие задачи, но не требуются для файла журнала, экспорта или дампа данных, которые помещаются на диск, но не помещаются в оперативную память.

S’abonner
Notification pour
guest

0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x