Python yield что это
Ключевое слово 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» для создания функции-оболочки, и результат останется прежним.
Как работает yield
На StackOverflow часто задают вопросы, подробно освещённые в документации. Ценность их в том, что на некоторые из них кто-нибудь даёт ответ, обладающий гораздо большей степенью ясности и наглядности, чем может себе позволить документация. Этот — один из них.
Вот исходный вопрос:
Как используется ключевое слово yield в Python? Что оно делает?
Например, я пытаюсь понять этот код (**):
Что происходит при вызове метода _get_child_candidates? Возвращается список, какой-то элемент? Вызывается ли он снова? Когда последующие вызовы прекращаются?
** Код принадлежит Jochen Schulz (jrschulz), который написал отличную Python-библиотеку для метрических пространств. Вот ссылка на исходники: http://well-adjusted.de/
Итераторы
Для понимания, что делает yield, необходимо понимать, что такое генераторы. Генераторам же предшествуют итераторы. Когда вы создаёте список, вы можете считывать его элементы один за другим — это называется итерацией:
Mylist является итерируемым объектом. Когда вы создаёте список, используя генераторное выражение, вы создаёте также итератор:
Всё, к чему можно применить конструкцию «for… in. », является итерируемым объектом: списки, строки, файлы… Это удобно, потому что можно считывать из них значения сколько потребуется — однако все значения хранятся в памяти, а это не всегда желательно, если у вас много значений.
Генераторы
Генераторы это тоже итерируемые объекты, но прочитать их можно лишь один раз. Это связано с тем, что они не хранят значения в памяти, а генерируют их на лету:
Всё то же самое, разве что используются круглые скобки вместо квадратных. НО: нельзя применить конструкцию for i in mygenerator второй раз, так как генератор может быть использован только единожды: он вычисляет 0, потом забывает про него и вычисляет 1, завершаяя вычислением 4 — одно за другим.
Yield
Yield это ключевое слово, которое используется примерно как return — отличие в том, что функция вернёт генератор.
В данном случае пример бесполезный, но это удобно, если вы знаете, что функция вернёт большой набор значений, который надо будет прочитать только один раз.
Чтобы освоить yield, вы должны понимать, что когда вы вызываете функцию, код внутри тела функции не исполняется. Функция только возвращает объект-генератор — немного мудрёно 🙂
Ваш код будет вызываться каждый раз, когда for обращается к генератору.
Теперь трудная часть:
В первый запуск вашей функции, она будет исполняться от начала до того момента, когда она наткнётся на yield — тогда она вернёт первое значение из цикла. На каждый следующий вызов будет происходить ещё одна итерация написанного вами цикла, возвращаться будет следующее значение — и так пока значения не кончатся.
Генератор считается пустым, как только при исполнении кода функции не встречается yield. Это может случиться из-за конца цикла, или же если не выполняется какое-то из условий «if/else».
Объяснение кода из исходного вопроса
Читатель может остановиться здесь, или же прочитать ещё немного о продвинутом использовании генераторов:
Контроль за исчерпанием генератора
Это может оказаться полезным для разных целей вроде управления доступом к какому-нибудь ресурсу.
Ваш лучший друг Itertools
Модуль itertools содержит специальные функции для работы с итерируемыми объектами. Желаете продублировать генератор? Соединить два генератора последовательно? Сгруппировать значения вложенных списков в одну строчку? Применить map или zip без создания ещё одного списка?
Просто добавьте import itertools.
Хотите пример? Давайте посмотрим на возможные порядки финиширования на скачках (4 лошади):
Понимание внутреннего механизма итерации
Итерация это процесс, включающий итерируемые объекты (реализующие метод __iter__()) и итераторы (реализующие __next__()). Итерируемые объекты это любые объекты, из которых можно получить итератор. Итераторы это объекты, позволяющие итерировать по итерируемым объектам.
Больше информации по данному вопросу доступно в статье про то, как работает цикл for.
Понимание 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, но итераторы и генераторы, на мой взгляд, фундаментальные понятия, которые были актуальны 20 лет назад и еще более актуальны сейчас, при этом за это время фактически не изменились.
Итераторы
Для начала вспомним, что из себя представляет паттерн «Итератор(Iterator)».
Назначение:
Существуют два вида итераторов, внешний и внутренний.
Внешний итератор — это классический (pull-based) итератор, когда процессом обхода явно управляет клиент путем вызова метода Next.
Внутренний итератор — это push-based-итератор, которому передается callback функция, и он сам уведомляет клиента о получении следующего элемента.
Классическая диаграмма паттерна “Итератор”, как она описана в небезызвестной книги «банды четырех»:
Aggregate — составной объект, по которому может перемещаться итератор;
Iterator — определяет интерфейс итератора;
ConcreteAggregate — конкретная реализация агрегата;
ConcreteIterator — конкретная реализация итератора для определенного агрегата;
Client — использует объект Aggregate и итератор для его обхода.
Пробуем реализовать на Python классический итератор
Конкретная реализация итератора для списка:
Конкретная реализация агрегата:
Теперь мы можем создать объект коллекции и обойти все ее элементы с помощью итератора:
А так как мы реализовали метод first, который сбрасывает итератор в начальное состояние, то можно воспользоваться этим же итератором еще раз:
Реализации могут быть разные, но основная идея в том, что итератор может обходить различные структуры, вектора, деревья, хеш-таблицы и много другое, при этом имея снаружи одинаковый интерфейс.
Протокол итерирования в Python
В книге «банды четырех» о реализации итератора написано:
Минимальный интерфейс класса Iterator состоит из операций First, Next, IsDone и CurrentItem. Но если очень хочется, то этот интерфейс можно упростить, объединив операции Next, IsDone и CurrentItem в одну, которая будет переходить к следующему объекту и возвращать его. Если обход завершен, то эта операция вернет специальное значения(например, 0), обозначающее конец итерации.
Именно так и реализовано в Python, но вместо специального значения, о конце итерации говорит StopIteration. Проще просить прощения, чем разрешения.
Сначала важно определиться с терминами.
Рассмотрим итерируемый объект (Iterable). В стандартной библиотеке он объявлен как абстрактный класс collections.abc.Iterable:
У него есть абстрактный метод __iter__ который должен вернуть объект итератора. И метод __subclasshook__ который проверяет наличие у класса метод __iter__. Таким образом, получается, что итерируемый объект это любой объект который реализует метод __iter__
Но есть один момент, это функция iter(). Именно эту функцией использует например цикл for для получения итератора. Функция iter() в первую очередь для получения итератора из объекта, вызывает его метод __iter__. Если метод не реализован, то она проверяет наличие метода __getitem__ и если он реализован, то на его основе создается итератор. __getitem__ должен принимать индекс с нуля. Если не реализован ни один из этих методов, тогда будет вызвано исключение TypeError.
Итого, итерируемый объект — это любой объект, от которого встроенная функция iter() может получить итератор. Последовательности(abc.Sequence) всегда итерируемые, поскольку они реализуют метод __getitem__
Теперь посмотрим, что с итераторами в Python. Они представлены абстрактным классом collections.abc.Iterator:
__next__ Возвращает следующий доступный элемент и вызывает исключение StopIteration, когда элементов не осталось.
__iter__ Возвращает self. Это позволяет использовать итератор там, где ожидается итерируемых объект, например for.
__subclasshook__ Проверяет наличие у класса метода __iter__ и __next__
Итого, итератор в python — это любой объект, реализующий метод __next__ без аргументов, который должен вернуть следующий элемент или ошибку StopIteration. Также он реализует метод __iter__ и поэтому сам является итерируемым объектом.
Таким образом можно реализовать итерируемый объект на основе списка и его итератор:
Функция next() вызывает метод __next__. Ей можно передать второй аргумент который она будет возвращать по окончанию итерации вместо ошибки StopIteration.
Прежде чем переходить к генераторам, рассмотрим еще одну возможность встроенной функции iter(). Ее можно вызывать с двумя аргументами, что позволит создать из вызываемого объекта(функция или класс с реализованным методом __call__) итератор. Первый аргумент должен быть вызываемым объектом, а второй — неким ограничителем. Вызываемый объект вызывается на каждой итерации и итерирование завершается, когда возбуждается исключение StopIteration или возвращается значения ограничителя.
Например, из функции которая произвольно возвращает 1-6, можно сделать итератор, который будет возвращать значения пока не «выпадет» 6:
Небольшой класс ProgrammingLanguages, у которого есть кортеж c языками программирования, конструктор принимает начальное значения индекса по названию языка и функция __call__ которая перебирает кортеж.
Можем перебрать все языки начиная с C# и до последнего:
Генераторы
С точки зрения реализации, генератор в Python — это языковая конструкция, которую можно реализовать двумя способами: как функция с ключевым словом yield или как генераторное выражение. В результате вызова функции или вычисления выражения, получаем объект-генератор типа types.GeneratorType.
В объекте-генераторе определены методы __next__ и __iter__, то есть реализован протокол итератора, с этой точки зрения, в Python любой генератор является итератором.
Концептуально, итератор — это механизм поэлементного обхода данных, а генератор позволяет отложено создавать результат при итерации. Генератор может создавать результат на основе какого то алгоритма или брать элементы из источника данных(коллекция, файлы, сетевое подключения и пр) и изменять их.
Ярким пример являются функции range и enumerate:
range генерирует ограниченную арифметическую прогрессию целых чисел, не используя никакой источник данных.
enumerate генерирует двухэлементные кортежи с индексом и одним элементом из итерируемого объекта.
Yield
Для начало напишем простой генератор не используя объект-генератор. Это генератор чисел Фибоначчи:
Но используя ключевое слово yield можно сильно упростить реализацию:
Любая функция в Python, в теле которой встречается ключевое слово yield, называется генераторной функцией — при вызове она возвращает объект-генератор.
Объект-генератор реализует интерфейс итератора, соответственно с этим объектом можно работать, как с любым другим итерируемым объектом.
Рассмотрим работу yield:
Создается стейт-машина в которой при каждом вызове __next__ меняется состояния и в зависимости от него вызывается тот или иной кусок кода. Если в функции yield в цикле, то соответственно состояние стейт-машины зацикливается пока не будет выполнено условие.
Свой вариант range:
Генераторное выражение (generator expression)
Если кратко, то синтаксически более короткий способ создать генератор, не определяя и не вызывая функцию. А так как это выражение, то у него есть и ряд ограничений. В основном удобно использовать для генерации коллекций, их несложных преобразований и применений на них условий.
В языках программирования есть такие понятия, как ленивые/отложенные вычисления(lazy evaluation) и жадные вычисления(eager/greedy evaluation). Генераторы можно считать отложенным вычислением, в этом смысле списковое включение(list comprehension) очень похожи на генераторное выражение, но являются разными подходами.
Первый вариант работает схожим с нашей функцией cool_range образом и может генерировать без проблем любой диапазон. А вот второй вариант создаст сразу целый список, со всеми вытекающими от сюда проблемами.
Yield from
Для обхода ограниченно вложенных структур, традиционный подход использовать вложенные циклы. Тот же подход можно использовать когда генераторная функция должна отдавать значения, порождаемые другим генератором.
Функция похожая на itertools.chain:
Но вложенные циклы можно убрать, добавив конструкцию yield from:
Основная польза yield from в создании прямого канала между внутренним генератором и клиентом внешнего генератора. Но это уже больше тема про сопрограммы(coroutines), которые заслуживают отдельной статьи. Там же можно обсудить методы генератора: close(), throw() и send().
И в заключении еще один пример. Функция принимающая итерируемый объект, с любым уровнем вложенности другими итерируемыми объектами, и формирующая плоскую последовательность:
Ключевое слово yield в Python
Python предоставляет программисту большой набор инструментов, один из которых — yield. Он заменяет обычный возврат значений из функции и позволяет сэкономить память при обработке большого объема данных.
yield – один из тех инструментов, использовать которые вовсе не обязательно. Всё, что можно реализовать с его помощью, можно сделать, используя обычный возврат return. Однако этот оператор позволяет не только сэкономить память, но и реализовать взаимодействие между несколькими последовательностями в пределах одного цикла.
Что такое yield и как это работает
Yield – ключевое слово, которое используется вместо return. С его помощью функция возвращает значение без уничтожения локальных переменных, кроме того, при каждом последующем вызове функция начинает своё выполнение с оператора yield.
Функция, содержащая yield в Python 3, называется генератором. Чтобы разобраться, как работает yield и зачем его используют, необходимо узнать, что такое генераторы, итераторы и итерации.
Но перед этим рассмотрим пример:
Тип полученного значения при вызове функции — это генератор. Один из способов получения значений из генератора — это их перебрать в цикле for. Им мы и воспользовались. Но можно его легко привести к списку, как мы сделали в статье про числа Фибоначчи.
Теперь разберемся, как это всё работает.
Что такое итерации
В программировании итерация — это процесс, в котором последовательно повторяется набор инструкций определенное количество раз или до тех пор, пока не будет выполнено условие.
Цикл — это повторяющаяся последовательность команд, каждый цикл состоит из итераций. То есть, одно выполнение цикла — это итерация. Например, если тело цикла выполнилось 5 раз, это значит, что прошло 5 итераций.
Итератор — это объект, позволяющий «обходить» элементы последовательностей. Программист может создать свой итератор, однако в этом нет необходимости, интерпретатор Python делает это сам.
Что такое генераторы
Генератор — это обычная функция, которая при каждом своём вызове возвращает объект. При этом в функции-генераторе вызывается next.
Отличие генераторов от обычной функции состоит в том, что функция возвращает только одно значение с помощью ключевого слова return, а генератор возвращает новый объект при каждом вызове с помощью yield. По сути генератор ведет себя как итератор, что позволяет использовать его в цикле for.
Программист может не использовать генераторы, однако в некоторых ситуациях оптимизировать программу можно только с их помощью.
Помимо yield, есть и другие способы создания генераторов, они описаны в статье о генераторах списка.
Функция next()
Эта функция позволяет извлекать следующий объект из итератора. То есть чтобы цикл перешел с текущей итерации на следующую, вызывается функция next(). Когда в итераторе заканчиваются элементы, возвращается значение, заданное по умолчанию, или возбуждается исключение StopItered.
На самом деле каждый объект имеет встроенный метод __next__, который и обеспечивает обход элементов в цикле, а функция next() просто вызывает его.
Вот пример использования next:
Преимущества использования yield
yield используют не потому, что это определено синтаксисом Python, ведь всё, что можно реализовать с его помощью, можно реализовать и с помощью обычного return.
Функция, которая обрабатывает большую последовательность и использует обычный return, требует от интерпретатора выделять ей много памяти. И если обычно такие функции не сильно влияют на производительность программы, то в проектах, содержащих последовательности с миллионами элементов, они потребляют очень много памяти.
Использование yield в языке программирования Python 3 позволяет не сохранять в память всю последовательность, а просто генерирует объект при каждом вызове функции. Это позволяет обойтись без использования большого количества оперативной памяти.
Сравнение производительности return и yield
Часто yield используют, когда необходимо прочитать большой текстовый файл. Чтобы наглядно показать преимущество использования генераторов, нужно создать два скрипта:
Затем скрипты должны обработать несколько файлов разных размеров, при этом получаются следующие результаты:
Размер файла | return | yield | ||
Память | Время | Память | Время | |
4 Кбайт | 5,3 Мбайт | 0.023 с | 5,42 Мбайт | 0.08 c |
324 Кбайт | 9,98 Мбайт | 0.028 с | 5,37 Мбайт | 0,32 с |
26 Мбайт | 392 Мбайт | 27 с | 5.52 Мбайт | 29.61 с |
263 Мбайт | 3,65 Гбайт | 273.56 с | 5,55 Мбайт | 292,99 с |
Видно, что в обоих случаях время увеличивается с примерно одинаковой скоростью, а количество потребляемой памяти сильно различается. Чем больше обрабатываемый файл, тем заметнее различие.
yield from
Многие считают, что yield from был добавлен в язык Python 3, чтобы объединить две конструкции: yield и цикл for, потому что они часто используются совместно, как в следующем примере:
Однако истинное предназначение нововведения немного в другом. Конструкция позволяет «вкладывать» один генератор в другой, то есть создавать субгенераторы.
yield from позволяет программисту легко управлять сразу несколькими генераторами, настраивать их взаимодействие и, конечно, заменить более длинную конструкцию for+yield, например:
Как видно из примера, yield from позволяет одному генератору получать значения из другого. Этот инструмент сильно упрощает жизнь программиста, особенно при асинхронном программировании.
Заключение
Использование генераторов в правильных местах позволяет значительно уменьшить потребление памяти, кроме того, взаимодействие с генераторами более прозрачно и легче поддается отладке.
yield – это лишь одно из многих полезных средств языка Python, которое может быть без проблем заменено обычным возвратом из функции с помощью return. Оно добавлено в язык, чтобы оптимизировать производительность программы, упростить код и его отладку и дать программистам возможность применять необычные решения в специализированных проектах.