Меню

Python как работать с генераторами

Генераторы в Python и их отличие от списков и функций

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

1. Что такое генератор в Python?

Генератор это подвид итерируемых объектов, как список или кортеж. Он генерирует для нас последовательность значений, которую мы можем перебрать. Эту последовательность можно использовать для итерации в цикле for, но нельзя проиндексировать (т. е., перебрать ее можно только один раз). Давайте посмотрим, как создается такая последовательность значений при помощи генератора.

а. Синтаксис генератора в Python 3

Для создания генератора в Python внутри функции вместо ключевого слова return используется ключевое слово yield. Обратите внимание на пример:

В этом примере мы определили генератор с именем counter() и назначили значение 1 локальной переменной i. Цикл while будет выполняться, пока i меньше или равно 10. Внутри цикла мы возвращаем (yield) значение i и увеличиваем его на единицу.

Затем мы используем этот генератор в цикле for.

b. Как работает генератор в Python

Чтобы разобраться в том, как работает этот код, давайте начнем с цикла for. Этот цикл выводит каждый элемент генератора (т. е., каждый элемент, возвращаемый генератором).

Мы начинаем с i=1. Таким образом, первый элемент, возвращаемый генератором, это 1. Цикл for выводит этот элемент на экран благодаря ключевому слову print. Затем i инкрементируется до 2. Весь процесс повторяется, пока i не инкрементируется до 11 (т. е., пока условие в цикле while не даст false).

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

Even
Even
Even
Even
Even
Even
Even
Even
Even
Even
Even
Even
Even

EvenTraceback (самый недавний вызов идет последним):

Поскольку 2 это четное число, 2%2 это всегда 0. Поэтому условие в цикле while всегда будет соблюдаться (всегда true). В результате генератор even() продолжает возвращать значение Even, пока мы не прервем выполнение цикла вручную (сочетанием клавиш Ctrl+C).

Обратите внимание, что генератор может содержать больше одного ключевого слова yield. Примерно так же, как функция может иметь больше одного ключевого слова return.

2. Возврат значений в список

Здесь все просто. Если вы примените функцию list() к вызову генератора, она вернет список возвращенных генератором значений, в том порядке, в котором они возвращались. В следующем примере генератор возвращает квадраты чисел, если эти квадраты четные.

Чтобы создать список из возвращаемых генератором значений, мы просто применяем функцию list() к вызову генератора. Мы не перебираем эти значения при помощи цикла for.

Как видите, для чисел в диапазоне 0-9 (не 10, потому что диапазон (10) это числа 0-9), четные квадраты это 0, 4, 16, 36 и 64. Остальные — 1, 9, 25, 49, 81 — нечетные. Поэтому они не возвращаются генератором.

3. Разница между списком и генератором в Python

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

4. Разница между генератором и функцией в Python

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

Когда интерпретатор доходит до ключевого слова return, выполнение функции полностью прекращается. Но когда он доходит до ключевого слова yield, программа приостанавливает выполнение функции и возвращает значение в итерируемый объект. После этого интерпретатор возвращается к генератору, чтобы повторить процесс для нового значения.

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

5. Генераторные выражения в Python

Для создания генераторов на скорую руку можно использовать выражения (как и для генераторов списка). Давайте возьмем для этого список:

at 0x003CC330>

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

Traceback (самый недавний вызов идет последним):

Вот и все, что мы хотели рассказать вам о генераторах в Python. Надеемся, вам понравилось наше объяснение.

6. Заключение

Теперь, когда вы знаете о преимуществах генераторов по сравнению со списками и функциями, вы понимание их важность. Что-то мы можем делать при помощи генератора, что-то — при помощи функции или даже генератора списка. Но использование генераторов наиболее эффективно.

Источник

Генераторы и итераторы в Python

Генератор в Python – одна из самых полезных и специальных функций. Мы можем превратить функцию в итератор, используя генераторы Python.

Базовая структура генератора

По сути, генератор в Python – это функция. Вы можете рассматривать следующее, как базовую структуру генератора.

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

Читайте также:  Генератор паролей 32 символа

Обычная функция возвращает какое-то значение, генератор возвращает какое-то значение и автоматически реализует next() и _iter_.

Генератор написан как обычные функции, но использует оператор yield всякий раз, когда они хотят вернуть какие-то данные. Каждый раз, когда функция next() вызывается для функции генератора, он возобновляет работу с того места, где он остановился (он запоминает все значения данных и какой оператор был выполнен последним).

Давайте теперь изучим каждую строку предыдущего кода:

  • Строка 2 – это объявление генератора, принимающего аргумент. Это необязательный аргумент, все зависит от программиста, который будет реализовывать генератор.
  • Строка 3, 5 – упоминает, что могут быть и другие утверждения.
  • Строка 4 – является важной частью вышеуказанной программы. Он говорит, что значение аргумента должно быть получено на основе некоторых условий, которые могут быть указаны в утверждениях.
  • Строка 8 – вызывает генератор с параметром 10, а строка 9 печатает возвращенный объект генератора.

Если вы запустите указанную выше программу, она выдаст следующее:

Обратите внимание, что приведенный выше результат не является значением. Фактически это указывает, где находится объект. Чтобы получить реальное значение, воспользуйтесь итератором. Затем next() будет вызываться для объекта, чтобы получить следующее полученное значение.

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

Затем он выведет значение 10, которое было передано в качестве аргумента и получено.

Получить значение генератора с точным вызовом next()

Теперь взгляните на следующую программу, в которой мы вызываем функцию next() генератора.

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

Приведенный выше код будет выводиться следующим образом:

Получение значения генератора с косвенным вызовом next()

Вы можете получить значения генератора, используя цикл for. Следующая программа показывает, как можно распечатать значения с помощью цикла for и генератора. Это даст тот же результат.

Порядок работы

Давайте теперь посмотрим, как на самом деле работает генератор. Обычная функция завершается после оператора return, а генератор – нет.

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

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

Вывод программы

Помните, что range() – это встроенный генератор, который генерирует число в пределах верхней границы.

Итератор – это объект, который используется для итерации по итерируемому элементу.

Большинство объектов в Python являются итеративными. Все последовательности, такие как Python String, Python List, Python Dictionary и т.д., являются повторяемыми. Что такое итератор? Предположим, группа из 5 мальчиков выстроилась в линию. Вы указываете на первого мальчика и спрашиваете его, как его зовут. Затем он ответил. После этого вы спрашиваете следующего мальчика и так далее. Изображение ниже иллюстрирует это.

В этом случае вы Итератор. Очевидно, группа мальчиков – повторяющийся элемент.

Протокол Iterator

Протокол Iterator в Python включает две функции. Один – iter(), другой – next(). В этом разделе мы узнаем, как пройти по итерируемому элементу, используя протокол Iterator.

В предыдущем разделе мы привели пример группы из 5 мальчиков и вас. Вы итератор, а группа мальчиков – повторяемый элемент. Зная имя одного мальчика, вы задаете тот же вопрос следующему мальчику.

После этого вы делаете это снова. Функция iter() используется для создания итератора повторяемого элемента. А функция next() используется для перехода к следующему элементу.

Пример

Если итератор превысит количество повторяемых элементов, метод next() вызовет исключение StopIteration. Смотрите код ниже для примера:

Создание

Однако вы можете создать свои собственные указанные итераторы в Python. Для этого вам необходимо реализовать класс.

Как мы уже говорили ранее, протокол состоит из двух методов. Итак, нам нужно реализовать этот метод.

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

Чтобы вызвать исключение, мы ограничиваем значение n ниже 10. Если значение n достигнет 10, это вызовет исключение. Код будет таким:

Зачем нужен итератор?

После прохождения предыдущего раздела у вас может возникнуть вопрос, зачем нам нужен Iterator.

Что ж, мы уже видели, что итератор может проходить по итерируемому элементу. Предположим, что в нашем предыдущем примере, если мы составим список чисел Фибоначчи, а затем проходим его через Iterator, это потребует огромной памяти. Но если вы создадите простой класс, вы сможете выполнить свою задачу, не потребляя столько памяти.

Источник

Генераторы Python. Их создание и использование

Приходилось ли вам когда-либо работать с настолько большим набором данных, что он переполнял память вашего компьютера? Или быть может у вас была сложная функция, для которой нужно было бы сохранять внутреннее состояние при вызове? А если при этом функция была слишком маленькой, чтобы оправдать создание собственного класса? Во всех этих случаях вам придут на помощь генераторы Python и ключевое слово yield.

Прочитав эту статью, вы узнаете:

  • Что собой представляют генераторы Python и как их использовать
  • Как задавать функции и выражения создающие генераторы
  • Как работает в Python ключевое слово yield
Читайте также:  Ремни генератора в наличии

Если вы являетесь Питонистом начального или среднего уровня и вы заинтересованы в том, чтобы научиться работать с большими наборами данных в питоновском стиле, то скорее всего это руководство для вас.

По ссылке ниже вы можете скачать копию файла с данными, используемыми в этом руководстве.

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

Функции генераторов (их описание можно почитать в 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() :

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

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

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

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

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

  • Использование yield приведет к созданию генератора.
  • Использование return приведет к возврату только первой строки файла.

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

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

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

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

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

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

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

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

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

Читайте также:  Генератор valeo для мерседес

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

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

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

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

Примечание: на практике вам вряд ли придется писать свой собственный бесконечный генератор последовательностей, по той простой причине, что есть уже очень эффективный генератор itertools.count() из модуля itertools.

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

Понимание работы генератора Python

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

Функции генераторов выглядят и действуют как обычные функции, но с одной определяющей особенностью. А именно, функция генератора используют ключевое слово yield вместо return . Давайте вспомним функцию генератора, которую мы написали ранее:

Это похоже на типичное определение функции, за исключением yield и кода, который следует за ним. Ключевое слово yield применяется там, где значение нужно отправить обратно вызывающей стороне. Но в отличие от return , выхода из функции в данном случае не происходит. Вместо этого, при возврате состояние функции запоминается. Более того, когда next() вызывается для объекта-генератора (явно или неявно в цикле for), ранее полученная переменная num увеличивается, а затем возвращается снова. Поскольку функции генератора похожи на другие функции и действуют подобным образом, вы можете предположить, что выражения создающие генераторы очень похожи на другие выражениях в Python создающие объекты.

Примечание. Если вы хотите больше узнать о генераторах списков, множеств и словарей в Python, можете прочитать статью Эффективное использование генераторов списков (англ).

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

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

Давайте для примера возьмем возведение в квадрат некоторых чисел:

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

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

Профилирование эффективности генератора

Ранее мы узнали, что использование генераторов является отличным способом оптимизации памяти. И хотя генератор бесконечной последовательности является наиболее ярким примером этой оптимизации, давайте рассмотрим еще один пример с возведением числа в квадрат и проверим размер полученных объектов.

Вы можете сделать это с помощью вызова функции sys.getsizeof () :

В этом случае размер списка, полученного с помощью выражения составляет 87 624 байта, а размер генератора — только 120. То есть, список занимает памяти в 700 раз больше, чем генератор! Однако нужно помнить одну вещь. Если размер списка меньше доступной памяти на работающей машине, тогда обработка его будет занимать меньше времени, чем аналогичная обработка генератора. Чтобы удостовериться в этом, давайте просуммируем результаты приведенных выше выражений. Вы можете использовать для анализа функцию cProfile.run () :

Здесь вы можете видеть, что суммирование всех значений, содержащихся в списке заняло около трети времени аналогичного суммирования с помощью генератора. Поэтому если скорость является для вас проблемой, а память — нет, то список, возможно, окажется лучшим инструментом для работы.

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

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

Источник

Adblock
detector