Найти в Дзене
Simple Prog

Генераторы в Python — что это и как с этим работать.

Работали ли вы с настолько огромным объемом данных, что он переполнял память компьютера? Я часто с этим сталкивался, пока не услышал о генераторах. Иногда на собеседованиях или иных тестах на оценку знаний предлагают такую задачу: Перед вами два выражения. Определите, что выведет каждое из них и в чем их разница. a = [i * i in range(5)] b = (i * i in range(5)) Если вы сомневаетесь в своих знаниях, то эта статья для вас. Использование Генераторов Функции генераторов (их описание можно почитать в PEP 255) представляют собой особый вид функций, которые возвращают «ленивый итератор». И хотя содержимое этих объектов вы можете перебирать также как и списки, но при этом, в отличие от списков, ленивые итераторы не хранят свое содержимое в памяти, что является главным их отличием. Чтобы составить общее представление об итераторах в Python взгляните на статью Python “for” Loops (Definite Iteration). Теперь, когда вы имеете примерное представление о том, чем является генератор, у вас наверняка по
Оглавление

Работали ли вы с настолько огромным объемом данных, что он переполнял память компьютера? Я часто с этим сталкивался, пока не услышал о генераторах.

Иногда на собеседованиях или иных тестах на оценку знаний предлагают такую задачу:

Перед вами два выражения. Определите, что выведет каждое из них и в чем их разница.
a = [i * i in range(5)]
b = (i * i in range(5))

Если вы сомневаетесь в своих знаниях, то эта статья для вас.

Использование Генераторов

Функции генераторов (их описание можно почитать в PEP 255) представляют собой особый вид функций, которые возвращают «ленивый итератор». И хотя содержимое этих объектов вы можете перебирать также как и списки, но при этом, в отличие от списков, ленивые итераторы не хранят свое содержимое в памяти, что является главным их отличием. Чтобы составить общее представление об итераторах в Python взгляните на статью Python “for” Loops (Definite Iteration).

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

Пример 1: Чтение больших файлов

Списки Python

Работа с потоками данных и большими файлами, такими например как CSV, являются наиболее распространенными вариантами использования генераторов. Давайте возьмем CSV файл (CSV является стандартным форматом для обмена данными, колонки в нем разделяются при помощи запятых). Предположим, что вы хотите посчитать количество имеющихся в нем рядов. Код ниже предлагает один из путей для, того, чтобы осуществить это:

Глядя на этот пример, вы можете предположить что csv_gen является списком. Для того чтобы заполнить этот список, csv_reader() открывает файл и загружает его содержимое в csv_gen. Затем программа перебирает список, увеличивая значение row_count для каждого следующего ряда.

Это вполне приемлемое решение, но будет ли этот подход работать, если файл окажется слишком большим? А что если файл окажется больше чем вся доступная память, которая есть в нашем распоряжении? Для того чтобы ответить на этот вопрос, предположим, что csv_reder() будет открывать файл и считывать его в массив.

Эта функция открывает данный файл и использует file.read() вместе со .split() для того, чтобы добавить каждый ряд данных как отдельный элемент списка. Если бы вы использовали эту версию cvs_reader() в блоке кода с подсчетом (вы его увидите далее), тогда бы вы увидели следующее сообщение:

В этом случае open() возвращает объект генератора, который вы можете «лениво» (не обсчитывая заранее) перебирать ряд за рядом. Тем не менее, file.read().split() загружает все данные в память сразу, вызывая ошибку памяти (MemoryError).

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

Генераторы Python

Давайте взглянем на новое определение функции csv_reader():

В этой версии вы открываете файл и проходите его содержимое, возвращая ряд за рядом. Этот код выводит следующий результат без каких-либо ошибок:

Row count is 64186394

Почему так получилось? Да потому что вы по сути превратили функцию csv_reader() в генератор. Эта версия кода открывает файл, проходит по строкам и извлекает для чтения лишь отдельный ряд, вместо того, чтобы возвращать весь файл целиком.

Также вы можете определить выражение создающее генератор, которое очень похоже по синтаксису на выражение создающее список. В таком виде вы можете использовать генератор без вызова функции:

csv_gen = (row for row in open(file_name))

Такой способ создания генератора csv_gen является более лаконичным.

Более подробно о yield мы расскажем позже, а пока запомните основные отличия между использованием ключевых слов yield и return:

  • Использование yield приведет к созданию генератора.
  • Использование return приведет к возврату только первой строки файла.
Источник: https://data-flair.training/blogs/wp-content/uploads/sites/2/2018/01/Python-Generators.jpg
Источник: https://data-flair.training/blogs/wp-content/uploads/sites/2/2018/01/Python-Generators.jpg

Пример 2: Создание бесконечной последовательности

Давайте теперь в качестве другого примера рассмотрим генератор бесконечной последовательности. В Python для того, чтобы получить конечную последовательность мы обычно вызываем функцию range(). Затем мы передаем ее значение как аргумент в функцию list():

Создание же бесконечной последовательности стопроцентно потребует от нас использования генератора. Причина проста — ограниченность памяти нашего компьютера.

Этот блок кода не велик и хорошо смотрится. Сперва, мы задаем переменную num и создаем бесконечный цикл. Затем мы немедленно извлекаем num с помощью yield в ее исходном состоянии (это во многом повторяет то, что делает range()). После этого мы увеличиваем num на 1.

Если вы попробуете запустить этот код в теле цикла for, то увидите, что на самом деле он бесконечный:

Эта программа будет исполняться, до тех пор, пока вы ее вручную не остановите.

Вместо использования Loop, вы также можете использовать на генераторе функцию next(). Это окажется особенно удобным при тестировании работы генератора в консоли:

Здесь у нас показан генератор, под названием gen, который мы можем вручную перебирать с помощью вызова функции next(). Это работает как отличная проверка. Она позволяет нам убедиться что генератор выдает результат, который мы от него ожидаем.

Пример 3: Нахождение палиндромов

Вы можете использовать бесконечные последовательности множеством различных способов. Одним из них, который мы отметим особенно, является создание детектора палиндромов.

Детектор палиндромов выявляет все последовательности букв и цифр, которые являются палиндромами. Это слова или числа, которые читаются одинаково вперед и назад, как «121» например. Сперва давайте зададим наш числовой детектор палиндромов:

Не особо беспокойтесь о понимании вычислений, лежащих в основе данного кода. Просто заметьте, что функция принимает введенное число, переворачивает его, и сравнивает с оригиналом. Теперь вы можете использовать генератор бесконечной последовательности для получения бегущего списка со всеми числовыми палиндромами:

В консоли выводятся только те номера, которые читаются одинаково и вперед и назад.

Источник: https://unsplash.com/photos/hEPKA9-1KRk
Источник: https://unsplash.com/photos/hEPKA9-1KRk

Создание генератора с помощью выражения

Вот мы и подошли к задаче из начала статьи. Продублирую:

Перед вами два выражения. Определите, что выведет каждое из них и в чем их разница.
a = [i * i in range(5)]
b = (i * i in range(5))

Вот, что будет, если попытаться вывести эти выражения:

Для первого объекта использовались квадратные скобки и это привело к созданию списка. Для второго использовались круглые скобки, и это привело к созданию генератора.

Запомните, что выражения создающие списки возвращают списки, в то время как выражения генераторов возвращают генераторы. Генераторы работают одинаково, независимо от того, построены они на основе функции или выражения.

Использование выражения позволяет вам задать простые генераторы одной строкой и также предполагает yield в конце каждой итерации. Ключевое слово yield, безусловно, является основой, на которой основывается вся функциональность генераторов.

Понравилась статья? Не забудь подписаться и оставить свое мнение в комментариях, обязательно прочту и отвечу.

Читайте также: