Ключевым средством для уменьшения нагрузки на оперативную память являются генераторы. В Python они представляют собой механизмы, способные экономить ресурсы памяти. Расскажем, как они работают и зачем нужны.
Что такое генератор Python и как это работает
Генераторы — это объекты, которые не вычисляют значения всех своих элементов сразу при создании. Они сохраняют в памяти только последнее вычисленное значение, правило перехода к следующему и условие, при котором выполнение прерывается. Вычисление следующего значения происходит только при вызове метода next(). Предыдущее значение при этом заменяется новым. Это отличает их от списков, которые хранят в памяти все свои элементы, и удалить их можно только программно. Использование таких объектов позволяет проводить ленивые вычисления, что способствует экономии памяти.
Что такое генераторы списков
Генератор списков представляет собой простой в чтении, компактный и изящный способ создания списка из любого существующего итерируемого объекта. В отличие от обычного генератора, он позволяет проще создавать новый список из значений уже имеющегося списка.
Обычно это одна строка кода, заключенная в квадратные скобки. Ее можно использовать для фильтрации, форматирования, изменения или выполнения других небольших задач с существующими итерируемыми объектами, такими как:
- списки,
- строки,
- множества,
- кортежи.
Как создать генератор
Рассмотрим пример: создадим объект gen с использованием генераторного выражения. Он будет вычислять квадраты чисел от 1 до 4 — такую последовательность создает функция range(1,5).
Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.
При четырех вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения: 1, 4, 9, 16. Причем в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.
Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрет из памяти последний элемент (число 16), выдаст исключение StopIteration, а потом перестанет функционировать. Независимо от количества вызовов next(gen) никакие значения больше не будут вычислены. Для повторного запуска генератора необходимо создать его снова.
Когда использовать генераторы
Использование генераторов вместо стандартных функций или списков может:
- значительно сократить расход памяти,
- увеличить скорость выполнения программы, особенно при работе с объемными данными.
Они оказываются незаменимыми в ситуациях, когда необходимо обрабатывать обширные объемы данных, избегая их одновременной загрузки в память. Например, их можно использовать для построчного чтения большого файла, минуя полную загрузку в оперативную память.
Часто генераторы применяются в веб-скрапинге. Они позволяют последовательно извлекать необходимые веб-страницы и анализировать их содержимое. Этот метод более эффективен, чем загрузка всех выбранных страниц в память с последующей обработкой в цикле.
Приведем примеры использования.
1. Предположим, у вас есть файл размером в десятки гигабайт. Вам необходимо выбрать строки, которые соответствуют определенному условию, а также сравнить их со строками другого крупного файла.
2. Еще один пример: вам нужно анализировать почти бесконечный поток данных, такой как показания счетчиков, биржевые котировки или сетевой трафик.
3. Пользователю нужно создать поток данных самостоятельно. Например, вы хотите рассчитать комбинаторные структуры для определения вероятности события, математическую последовательность или последовательность случайных чисел.
Что же делать в такой ситуации? Хранение таких огромных объемов данных на компьютере становится проблематичным: они не помещаются в оперативную память, а в некоторых случаях даже на жесткий диск. Единственное решение — обрабатывать информацию по частям, чтобы избежать переполнения памяти. Для этого и используются генераторы на Питоне.
Что такое генераторные выражения
Генераторные выражения представляют собой упрощенную форму функций-генераторов, которые также создают генераторы. Функция-генератор отличается от обычной функции тем, что вместо оператора return она использует оператор yield. Если оператор return завершает выполнение функции, то оператор yield лишь временно приостанавливает ее, возвращая при этом определенное значение.
При первом вызове метода next() выполняется код функции до первого оператора yield. При последующих вызовах до завершения выполняются операторы после каждого оператора yield, пока он не встретится снова.
Оператор yield очень гибок. Его можно использовать несколько раз в теле функции. В таком случае операторы yield служат разделителями кода: при первом вызове метода next() выполняется код до первого оператора yield, а при последующих вызовах — код между операторами yield. При этом в генераторной функции не обязательно должен присутствовать цикл; все значения будут вычислены независимо.
Как создать бесконечную последовательность
Давайте рассмотрим, как можно использовать генератор для создания математических последовательностей, например, программы, которая генерирует простые числа (то есть числа, которые делятся только на себя и на 1).
Наша программа будет последовательно анализировать целые числа, начиная с 2. Для каждого числа n программа будет проверять его наличие делителей в диапазоне от 2 до квадратного корня из n. Если такие делители найдены, программа перейдет к следующему числу. Если делителей нет, то n считается простым числом, и программа выведет его на экран.
Этот код создает бесконечную последовательность простых чисел без верхнего предела. Остановить его можно только вручную.
Аналогичным образом можно создавать последовательности случайных чисел, комбинаторные структуры, рекуррентные ряды, такие как ряд Фибоначчи, и множество других последовательностей.
Методы генераторов
В прошлом в Питоне использовался только один метод next(), однако с появлением Python 2.5 было добавлено еще три метода:
- close(): останавливает выполнение генератора;
- throw(): позволяет бросить исключение;
- send(): метод, который позволяет отправлять значения генератору.
Давайте рассмотрим примеры:
1. Программа создает два генератора, которые возвращают бесконечную последовательность квадратов чисел. Их выполнение прекращается с использованием методов. close() и .throw().
2. Другая программа создает еще два: они возвращают бесконечную последовательность квадратов чисел. Их выполнение также завершается с помощью методов .close() и .throw().
С использованием этих методов возможно создание сопрограмм, также известных как корутины. Это функции, которые могут приостанавливать и возобновлять свою работу, а также принимать и передавать значения. В Питоне они часто используются для обработки потоков данных в корпоративной среде многозадачности. В руках разработчика оказываются инструменты для разработки сложных программ с разветвленной логикой для обработки потоков данных.
Выводы
- Генераторы представляют собой особые конструкции в языке Python, которые позволяют быстро создавать списки. Это весьма удобный инструмент, но следует стремиться к их максимальной простоте, иначе читаемость кода значительно пострадает.
- Для списков используются объекты, которые итеративно возвращают значения и сохраняют свое состояние внутри себя.
- Синтаксис генераторного выражения аналогичен функции, однако вместо return в нем используется yield.
- Одновременно может быть несколько операторов yield, и он может получать значения через. send(). Но это уже более сложные техники, которые не обязательно осваивать сразу. Если вы только начинаете изучать Python — можно отложить их изучение на потом.
- Изучение генераторов является первым шагом в освоении последовательной обработки огромных потоков данных, таких как трейдинг и технический анализ на бирже.
- Даже если не рассматривать глобальные задачи, использование генераторных скриптов представляет собой способ избежать лишнего копирования данных в память с созданием чистого кода.