yield python что делает
Понимание yield в Python
Ключевое слово yield в Python используется для создания генераторов. Генератор – это коллекция, которая продуцирует элементы на лету и может быть повторена только один раз. С помощью генераторов можно повысить производительность приложения и снизить потребление памяти по сравнению с обычными коллекциями.
В этой статье будет рассказано, как использовать ключевое слово yield в Python и как именно оно работает. Но сначала давайте поймём разницу между простым списком и генератором, а затем посмотрим, как yield можно использоваться для создания более сложных генераторов.
Различия между списком и генератором
В следующих примерах будет создан список и генератор, чтобы увидеть их отличия. Сначала создадим простой список и проверим его тип:
При запуске этого кода, программа вернёт результат «list». Теперь давайте переберём все элементы в списке squared_list.
Приведенный выше сценарий вернёт следующие результаты:
Теперь создадим генератор и выполнить ту же задачу:
Генератор создаётся подобно коллекции списка, но вместо квадратных скобок нужно использовать круглые скобки. Приведенный выше сценарий вернёт значение «generator» как тип переменной squared_gen. Теперь давайте переберём элементы генератора с помощью цикла for.
Выходные данные такие же, как у списка. Так в чем же разница? Одно из главных отличий заключается в том, как в список и генератор хранят элементы в памяти. Списки хранят все элементы в памяти сразу, тогда как генераторы «создают» каждый элемент на лету, отображая их, а затем перемещаются к следующему элементу, удаляя предыдущий элемент из памяти.
Один из способов проверить это, узнать длину как списка, так и генератора, который только что создали. Функции len(squared_list) вернет 5, а len(squared_gen) выдаст ошибку отсутствия длины у генератора. Кроме того, список можно перебирать столько раз, сколько захотите, но генератор можно перебирать только один раз. Для повторной итерации необходимо создать генератор снова.
Использование ключевого слова yield
Теперь мы знаем разницу между простыми коллекциями и генераторами, давайте посмотрим, как yield может помочь нам определить генератор.
В предыдущих примерах был создан генератор неявно, используя синтаксис генераторов списков. Однако в более сложных сценариях необходимо создавать функции, которые возвращают генератор. Ключевое слово yield, в отличие от оператора return, используется для превращения обычной функции Python в генератор. Оно используется в качестве альтернативы одновременному возвращению целого списка.
Опять же, давайте сначала посмотрим, что возвращает наша функция, если не использовать ключевое слово yield. Выполните следующий сценарий:
В этом скрипте создается функция cube_numbers, которая принимает список чисел, вычисляет их куб и возвращает вызывающему объекту список целиком. При вызове этой функции список кубов возвращается и сохраняется в переменную cubes. Как видно из вывода, возвращаемые данные – это список целиком:
Теперь, изменим сценарий, так чтобы он возвращал генератор.
В приведенном выше скрипте функция cube_numbers возвращает генератор вместо списка кубов чисел. Создать генератор с помощью ключевого слова yield очень просто. Здесь нам не нужна временная переменная cube_list для хранения куба числа, поэтому даже наш метод cube_numbers проще. Кроме того, не используется оператор return, но вместо него используется слово yield для возвращения куба числа внутри цикла.
Теперь, когда функция cube_number возвращает генератор, проверим его, запустив код:
Несмотря на то, что был произведён вызов функции cube_numbers, она фактически не выполняется на данный момент времени, и в памяти еще нет элементов.
Получение значение из генератора:
Вышеуказанная функция возвратит «1». Теперь, когда снова вызывается next генератора, функция cube_numbers возобновит выполнение с того места, где она ранее остановилась на yield. Функция будет продолжать выполняться до тех пор, пока снова не найдет yield. Следующая функция будет продолжать возвращать значение куба по одному, пока все значения в списке не будут проитерированы.
Как только все значения будут проитерированы, следующий вызов функции создаст исключение StopIteration. Важно отметить, что генератор кубов не хранит какие-либо элементы в памяти, а значения в кубе вычисляются во время выполнения, возвращаются и забываются. Используется только дополнительная память для хранения данных состояния самого генератора, которая, как правило, гораздо меньше, чем полный список. Это делает генераторы идеально подходящими для ресурсоемких задач.
Вместо того, чтобы использовать next итератора, можно также использовать цикл for для перебора значений генераторов. При использовании цикла for за кулисами вызывается next итерации, пока не будут возвращены все элементы генератора.
Оптимизация производительности
Как упоминалось ранее, генераторы очень удобны, когда дело доходит до задач, активно расходующих память, так как они не хранят все элементы коллекции в памяти, а генерируют элементы на лету и удаляют их, как только итератор переходит к следующему элементу.
В предыдущих примерах разница в производительности простого списка и генератора не была видна, так как размеры списка были малы. В этом разделе рассмотрим некоторые примеры, где можно сравнить производительность списков и генераторов.
В приведенном ниже коде представлена функция, которая возвращает список, содержащий 1 миллион фиктивных объектов car. Рассчитаем память, процессорное время до и после вызова функции.
Взглянем на следующий код:
Примечание: возможно, придется выполнить pip install psutil, чтобы этот код был работоспособен.
На компьютере автора статьи получены следующие результаты (в вашем случае результаты могут быть иными):
До создания списка потребляемая память процесса составляла 8 МБ, а после создания списка с 1 миллионом элементов занимаемая память подскочила до 334 МБ. Кроме того, для создания списка было затрачено 1.58 секунды.
Теперь, повторим процесс, но заменив список на генератор. Выполним следующий сценарий:
Результаты при выполнении вышеуказанного скрипта:
Из выходных данных видно, что при использовании генераторов разница в потреблении памяти незначительна (она остается на уровне 8 МБ), так как генераторы не хранят элементы в памяти. Кроме того, время, затраченное на вызов функции генератора, составило всего 0,000003 секунды – это намного меньше затраченного времени, по сравнению с списком.
Вывод
Надеюсь, после прочтения этой статьи вы лучше стали понимать ключевое слово yield, в том числе, как его использовать, для чего оно используется. Генераторы Python – отличный способ улучшить производительность программ, и они очень просты в использовании, но понимание того, когда их использовать, является проблемой для многих начинающих программистов.
Генераторы Python: что это такое и зачем они нужны
Генераторы используют, чтобы оперативная память не давилась большими объёмами информации. В Python это фишки, экономящие память.
Допустим, у вас есть файл, который весит десяток гигабайт. Из него нужно выбрать и обработать строки, подходящие под какое-то условие, а то и сравнить со строками другого большого файла.
Другой пример: нужно проанализировать практически бесконечный поток данных. Это могут быть, например, показания счётчиков, биржевые котировки, сетевой трафик.
А может, нужно создать поток данных самостоятельно: рассчитать комбинаторную структуру для определения вероятности какого-то события, математическую последовательность или последовательность случайных чисел.
Что делать? Хранить такие объёмы данных в компьютере нереально: они не поместятся в оперативную память — а некоторые и на жёсткий диск. Выход один — обрабатывать информацию небольшими порциями, чтобы не вызывать переполнения памяти. В Python на этот случай есть специальный инструмент — генераторы.
Программист, консультант, специалист по документированию. Легко и доступно рассказывает о сложных вещах в программировании и дизайне.
Что такое генератор и как он работает?
Этим генераторы отличаются от списков — те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память.
Рассмотрим пример: создадим объект-генератор gen с помощью так называемого генераторного выражения. Он будет считать квадраты чисел от 1 до 4 — такую последовательность создаёт функция range(1,5).
Когда мы выведем на консоль переменную gen, то увидим лишь сообщение, что это объект-генератор.
При четырёх вызовах метода next(a) будут по одному рассчитываться и выводиться на консоль значения генератора: 1, 4, 9, 16. Причём в памяти будет сохраняться только последнее значение, а предыдущие сотрутся.
Когда мы попытаемся вызвать next(gen) в пятый раз, генератор сотрёт из памяти последний элемент (число 16) и выдаст исключение StopIteration.
Всё! Генератор больше не работает. Сколько бы мы ни вызывали next(gen), ничего считаться не будет. Чтобы запустить генератор ещё раз, придётся создавать его заново.
И что, для вычисления генератора придётся много раз вызывать next()?
Нет, значения можно вычислять в цикле for. В этом случае метод next() вызывается неявно. Например:
Когда весь цикл пройден, произойдёт исключение StopIteration. Хотя на консоль сообщение об этом не выводится, но генератор помнит о нём и больше работать не будет. То есть цикл for можно запускать только один раз, во второй раз не получится. Нельзя об этом забывать.
И чем помогут генераторы в наших задачах?
Для этого сначала рассмотрим упрощённый способ создания генератора — с помощью генераторного выражения.
Генераторные выражения позволяют создавать объект-генератор в одну строчку. В общем случае их пишут по шаблону:
( выражение for j in итерируемый объект if условие)
Где for, in, if — ключевые слова, j — переменная.
Пример генераторного выражения мы рассмотрели выше. Теперь посмотрим, как можно применить его для обработки большого файла.
Перед нами задача: на сервере есть огромный журнал событий log.txt, в котором хранятся сведения о работе какой-то системы за год. Из него нужно выбрать и обработать для статистики данные об ошибках — строки, содержащие слово error.
Такие строки можно выбрать и сохранить в памяти с помощью списка:
Здесь path — путь к файлу log. В результате сформируется список вида:
[строка1, строка2, строка3, ….. ]
В списке e_l содержатся все строки со словом error, они записаны в память компьютера. Теперь их можно обработать в цикле. Недостаток метода в том, что, если таких строк будет слишком много, они переполнят память и вызовут ошибку MemoryError.
Переполнения памяти можно избежать, если организовать поточную обработку данных с использованием объекта-генератора. Мы создадим его с помощью генераторного выражения (оно отличается от генератора списка только круглыми скобками).
Рассмотрим следующий код:
Этот метод не вызывает переполнения, так как в каждый момент времени в памяти находится только одна строка. При этом нужный для работы объём памяти не зависит от размера файла и количества строк, удовлетворяющих условию.
Как ещё можно создавать генераторы?
Генераторные выражения — это упрощённый вариант функций-генераторов, также создающих генераторы.
Функция-генератор отличается от обычной функции тем, что вместо команды return в ней используется yield. И если return завершает работу функции, то инструкция yield лишь приостанавливает её, при этом она возвращает какое-то значение.
При первом вызове метода next() выполняется код функции с первой команды до yield. При втором next() и последующих до конца генератора — код со следующей после yield команды и до тех пор, пока yield не встретится снова.
Чтобы было понятнее, рассмотрим небольшой пример:
Здесь функция f_gen(5) при вызове создаёт генератор a. Мы видим это, когда выводим a на консоль.
Посчитаем значения генератора в цикле for.
Как видим, значения переменных n и s между вызовами сохраняются.
Yield — инструмент очень гибкий. Его можно несколько раз использовать в коде функции-генератора. В этом случае команды yield служат разделителями кода: при первом вызове метода next() выполняется код до первого yield, при следующих вызовах — операторы между yield. При этом в генераторной функции необязательно должен быть цикл, все значения генератора и так посчитаются.
Как создать бесконечную последовательность
Рассмотрим, как можно с помощью генератора создать математическую последовательность, например, программу, генерирующую простые числа (напоминаем, это числа, не имеющие делителей, кроме 1).
Наша программа будет последовательно анализировать целые числа больше 1. Для каждого числа n программа ищет делители в диапазоне от 2 до √n. Если делители есть, программа переходит к следующему числу. Если их нет, значит, n — число простое, и программа выводит его на печать.
Этот код выдаёт бесконечную последовательность простых чисел без ограничения сверху. Остановить его можно только вручную.
Подобным образом с помощью генераторов можно создавать ряды случайных чисел, комбинаторные структуры, рекуррентные ряды, например, ряд Фибоначчи и другие последовательности.
Какие ещё методы есть у генераторов?
Когда-то был один next(), но в Python 2.5 появилось ещё три метода:
Рассмотрим пару небольших примеров.
С помощью этих методов можно создавать сопрограммы, или корутины, — это функции, которым можно передавать значения, приостанавливать и снова возобновлять их работу. Их обычно используют в Python для анализа потоков данных в корпоративной многозадачности. Генераторы позволяют создавать сложные разветвлённые программы для обработки потоков.
Что ещё можно сказать
С изучения генераторов начинается освоение последовательной обработки гигантских потоков данных. Это может быть, например, трейдинг и технический анализ в биржевых операциях.
Но даже если не говорить о глобальных задачах, скрипты с применением генераторов — это способ избежать копирования данных в память. Генераторы позволяют экономить ресурсы компьютера и создавать красивый чистый код.
Изучить генераторы и другие объекты Python можно на курсах в Skillbox. Вы получите серьёзные теоретические знания и практический опыт. С самого начала обучения будете участвовать в реальных проектах. Те, кто успешно окончит курсы, станут программистами middle-уровня, а мы поможем найти хорошую работу.
Подробно про генераторы Python – что такое и как работают
Что такое генераторы в Python?
Генераторы Python – это функции, которые возвращают объект обхода и используются для создания итераторов, просматривают сразу все элементы. Генератор также может быть выражением, синтаксис которого аналогичен пониманию списка в Python.
Создание итерации в Python сопряжено с большими трудностями; нам нужно реализовать методы __iter __() и __next __() для отслеживания внутренних состояний.
Создание итераторов – длительный процесс. Вот почему генератор играет важную роль в упрощении этого процесса. Если в итерации не найдено значение, возникает исключение StopIteration.
Как создать функцию генератора в Python?
Создать генератор на Python довольно просто. Он похож на обычную функцию, определяемую ключевым словом def, и использует ключевое слово yield вместо return. Или мы можем сказать, что если тело любой функции содержит оператор yield, он автоматически становится функцией-генератором. Рассмотрим следующий пример:
Yield вместо return
Оператор yield отвечает за управление потоком функции генератора. Он приостанавливает выполнение функции, сохраняя все состояния и уступая вызывающему. Позже он возобновляет выполнение при вызове следующей функции.
Оператор return возвращает значение и завершает работу всей функции, оператор return может использоваться в функции только один раз. Оператор yield в функции генератора мы можем использовать неоднократно.
Рассмотрим следующий пример.
Разница между функцией генератора и нормальной функцией
Генератор выражения
Мы можем легко создать выражение генератора без использования пользовательской функции. Это то же самое, что и лямбда-функция, которая создает анонимную функцию; выражения генератора создают анонимную функцию генератора.
Представление выражения генератора похоже на понимание списка Python. Единственное отличие состоит в том, что квадратные скобки заменены круглыми скобками. Понимание списка вычисляет весь список, тогда как выражение генератора вычисляет один элемент за раз.
Рассмотрим следующий пример:
В приведенной выше программе list comprehension вернуло список элементов в третьей степени, тогда как выражение генератора вернуло ссылку на вычисленное значение. Вместо применения цикла for мы также можем вызвать next() для объекта-генератора. Рассмотрим другой пример:
Примечание: – Когда мы вызываем next(), Python вызывает __next __() для функции, в которую мы передали его в качестве параметра.
В приведенной выше программе мы использовали функцию next(), которая вернула следующий элемент списка.
Пример программы для печати таблицы заданного числа с помощью генератора:
В приведенном выше примере функция генератора выполняет итерацию с использованием цикла for.
Преимущества
Есть различные преимущества генераторов. Некоторые из них приведены ниже:
Генераторы проще реализовать по сравнению с итератором. В итераторе мы должны реализовать функцию __iter __() и __next __().
Генераторы эффективно используют память для большого количества последовательностей. Обычная функция возвращает последовательность из списка, которая создает всю последовательность в памяти перед возвратом результата, а функция генератора вычисляет значение и приостанавливает их выполнение и возобновляется для следующего вызова.
Генератор бесконечной последовательности – отличный пример оптимизации памяти. Давайте обсудим это в приведенном ниже примере, используя функцию sys.getsizeof().
Из вышеприведенного вывода видно, что для list comprehension используется 4508 байт памяти, тогда как generator expression использует 56 байт памяти. Это означает, что объекты-генераторы намного эффективнее, чем сжатие списков.
Data Pipeline предоставляет возможность обрабатывать большие наборы данных или поток данных без использования дополнительной памяти компьютера.
Предположим, у нас есть файл журнала известного ресторана. В файле журнала есть столбец(4-й столбец), в котором отслеживается количество гамбургеров, проданных каждый час, и мы хотим просуммировать его, чтобы найти общее количество гамбургеров, проданных за 4 года. В этом сценарии генератор может создать конвейер с серией операций. Ниже приведен его код:
Генератор может производить бесконечное количество предметов. Бесконечные последовательности не могут содержаться в памяти, и поскольку генераторы производят только один элемент за раз, рассмотрим следующий пример:
В этом руководстве мы узнали о генераторах Python.
Ключевое слово yield в Python
Сравнение yield и return
Пример yield
Допустим, у нас есть функция, которая возвращает список случайных чисел.
Он отлично работает, когда значение «count» не слишком велико. Если мы укажем count как 100000, тогда наша функция будет использовать много памяти для хранения такого количества значений в списке.
В этом случае полезно использовать ключевое слово yield для создания функции генератора. Давайте преобразуем функцию в функцию генератора и воспользуемся итератором генератора для получения значений одно за другим.
Один из самых популярных примеров использования функции генератора — чтение большого текстового файла. В этом примере я создал два скрипта на 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: Объяснение работы yield, итераторов и генераторов
Для понимания что делает «yield», вы должны понимать что такое генераторы. Для понимания что такое генераторы — должны знать об итераторах и итерируемых объектах.
Итерируемые объекты (iterables)
Хочется назвать их неправильным, с точки зрения русского языка, словом «итерабельные» — т.е. те, по которым может происходить итерация. Но, правильнее будет назвать их «итерируемые», хотя лично меня это слово слегка путает.
Когда вы создаёте список (list) вы можете считывать его элементы по одному — это называется итерацией.
Lst — итерируемый объект (iterable). Когда вы используете списковые выражения (list comprehensions), вы создаёте список — итерируемый объект:
Любое объект который вы можете использовать в конструкции «for … in …» является итерирумым: списки, строки, файловые объекты и т.п.. Итерирумые объекты достаточно удобны потому что вы можете считывать из них столько данных, сколько вам необходимо, но при этом вы храните все значения последовательности в памяти и это не всегда приемлемо, особенно если вы имеете достаточно большие последовательности.
Генераторы
Генераторы — итерируемые объекты, но, в общем случае, вы можете их использовать только один раз. Это связано с тем, что они не хранят все значения в памяти, а генерируют значения «на лету» — по мере запроса:
Код выглядит почти так же, как и в предыдущем примере, только вместо квадратных скобочек («[ ]») были использованы круглые («( )»). Заметьте, что вы не можете выполнить цикл по generator во второй раз, поскольку ничего в памяти не хранится, попытка пройтись второй раз будет просто проигнорирована, т.к. generator выбросит при первом запросе на получение следующего значения StopIterationError, однако, вы это не заметите, если будете использовать цикл for, это исключение будет перехвачено и интерпретировано как конец цикла). Но вручную это можно проверить:
next — это метод для получения следующего значения генератора, если вы его используете не в цикле for.
Yield
Yield — это ключевое слово которое используется так же, как и слово return. Разница в том, что функция при этом начинает возвращать генератор вместо значения.
В данном случае, с практической точки зрения, это бесполезный пример. Ощутимую пользу вы получите в ситуации, когда ваша функция должна будет возвращать достаточно большой объём данных, но использовать их надо будет только один раз.
При первом исполнении кода тела функции код будет выполнен с начала и до первого встретившегося оператора yield. После этого будет возвращено первое значение и выполнение тела функции опять приостановлено. Запрос следующего значения у генератора во время итерации заставит код тела функции выполняться дальше (с предыдущего yield’а), пока не встретится следующий yield. Генератор считается «закончившимся» в случае если при очередном исполнении кода тела функции не было встречено ни одного оператора yield.