Меню

Функция генератор python yield



Ключевое слово yield в Python

Сравнение yield и return

Пример yield

Допустим, у нас есть функция, которая возвращает список случайных чисел.

Он отлично работает, когда значение «count» не слишком велико. Если мы укажем count как 100000, тогда наша функция будет использовать много памяти для хранения такого количества значений в списке.

В этом случае полезно использовать ключевое слово yield для создания функции генератора. Давайте преобразуем функцию в функцию генератора и воспользуемся итератором генератора для получения значений одно за другим.

  • Обратите внимание, что тип nums_generator — генератор.
  • Первый оператор печати выполняется только один раз, когда первый элемент извлекается из генератора.
  • Как только все элементы получены из функции генератора, выполняется оставшийся код функции генератора. Вот почему второй оператор печати печатается только один раз и в конце цикла for.

Один из самых популярных примеров использования функции генератора — чтение большого текстового файла. В этом примере я создал два скрипта на Python.

Я использую модуль ресурсов Python для печати использования памяти и времени обоих сценариев.

У меня четыре текстовых файла разного размера.

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

Вот данные в табличном формате для лучшего понимания.

Размер Return Statement Generator Function
4 KB Memory: 5.3 MB, Time: 0.023s Memory: 5.42 MB, Time: 0.027s
324 KB Memory: 9.98 MB, Time: 0.028s Memory: 5.37 MB, Time: 0.32s
26 MB Memory: 392.8 MB, Time: 27.03s Memory: 5.52 MB, Time: 29.61s
263 MB Memory: 3.65 GB, Time: 273.56s Memory: 5.55 MB, Time: 292.99s

Таким образом, функция генератора занимает немного больше времени, чем оператор return. Это очевидно, потому что он должен отслеживать состояние функции при каждом вызове итератора next().

Но с ключевым словом yield преимущества памяти огромны. Использование памяти прямо пропорционально размеру файла с помощью оператора return. Это почти постоянно с функцией генератора.

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

Пример отправки вывода Python

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

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

В противном случае мы получим TypeError: невозможно отправить значение, отличное от None, только что запущенному генератору.

Выход из выражения

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

Мы можем использовать «yield from» в функции generate_ints(), чтобы создать двунаправленное соединение между вызывающей программой и суб-итератором.

Фактическая выгода от «yield from» видна, когда нам нужно отправить данные в функцию генератора.

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

Это очень много кода для создания функции-оболочки. Мы можем просто использовать здесь «yield from» для создания функции-оболочки, и результат останется прежним.

Источник

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

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

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

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

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

Читайте также:  Ремни генератора для бмв е39

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

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

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

Читайте также:  Lost ark генератор имен

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

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

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

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

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

Пример 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 , безусловно, является основой, на которой основывается вся функциональность генераторов. В следующих статьях мы углубимся в его работу.

Источник

Функция генератора в Python.

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

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

Вот самый простой пример функции генератора:

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

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

При выполнении выражения yield генератор выводит значение i , аналогичное оператору return . Разница между yield и оператором return заключается в том, что при достижении выхода, состояние выполнения генератора приостанавливается и локальные переменные сохраняются. При следующем вызове метода генератора __next__() функция возобновит свое выполнение.

Вот пример использования генератора generate_ints() :

Также можно написать для i в generate_ints(5) или a, b, c = generate_ints(3) .

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

Источник