🐍 Что такое yield в Python? Самый популярный вопрос на Стаковерфлоу по Питону
Самый популярный Python-вопрос на Stackoverflow связан с ключевым словом yield. Разберемся с его назначением и особенностями использования.
Официальная документация Python содержит достаточно подробное описание всех функции языка и немало примеров. Тем не менее назначение некоторых ключевых слов ставит начинающих разработчиков в тупик. Прежде всего это касается yield
– не случайно вопрос о нем остается самым популярным на Stackoverflow.
Вопрос звучит так:
Как используется ключевое слово yield
в Python и что именно оно делает? Я пытаюсь понять, как работает, к примеру, этот код:
Он вызывается так:
Что происходит в момент вызова метода _get_child_candidates
? Что он возвращает – список или элемент? Вызывается ли метод повторно, и когда прекращаются последующие вызовы?
А вот и лучший ответ на вопрос о yield
в Python:
Для понимания того, что делает yield
, необходимо четко представлять, как работают генераторы и итераторы.
Итераторы
Когда вы создаете список, входящие в него элементы можно перебирать один за другим – это и есть итерация:
Список mylist – итерируемый объект. Итератор создается во время генерации списка с помощью спискового включения:
Любые объекты, для которых можно использовать цикл for
, являются итерируемыми – списки, строки, файлы. Итерируемые объекты очень удобны, потому что они не ограничивают количество повторных считываний данных. Однако вся информация находится в оперативной памяти, и при большом объеме данных это нежелательно.
Генераторы
Генераторы также относятся к итерируемым объектам, однако данные из них можно считать только один раз. Генераторы не хранят значения в памяти, а создают их на лету:
Генераторы используются так же, как и списковые включения, отличие заключается в применении круглых скобок ()
вместо квадратных []
. Кроме того, в отличие от списка, сгенерированного с помощью спискового включения, к генератору нельзя обратиться повторно – вычисляя каждый последующий элемент, генератор «забывает» о предыдущем.
yield
Ключевое слово yield
используется в функциях так же, как и return
– для возвращения результата работы. Разница заключается в том, что yield
возвращает генератор.
Использовать yield
вместо return
стоит в тех случаях, когда функция возвращает большой объем данных, которые достаточно прочитать один раз.
Для эффективного использования нужно понимать главную особенность yield
: при вызове функции код в теле функции не исполняется. Функция просто возвращает объект-генератор. Код вызывается каждый раз, когда for
обращается к генератору. При первом запуске функции она будет исполняться, пока не дойдет до yield
, после чего вернет первое значение из цикла. При каждом последующем вызове будет происходить следующая итерация и возвращение значения цикла. Процесс будет повторяться, пока генератор не окажется пустым. Генератор считается пустым, если функция не встречает yield
– это происходит либо в конце цикла, либо при невыполнении условий if
и else
.
Пояснение кода, приведенного в вопросе
Генератор:
Вызов:
Как работает код
Цикл проводит итерацию списка, при этом список расширяется во время перебора. Это быстрый способ обхода сгруппированных значений, хотя существует небольшая опасность превращения цикла в бесконечный. В таком случае candidates.extend(node._get_child_candidates(distance, min_dist, max_dist)) завершит использование всех значений генератора, но цикл while
не остановится на этом, а продолжит процесс создания новых объектов-генераторов, поставляющих значения, отличающиеся от предыдущих (потому что они относятся к другим узлам).
В коде используется метод extend()
, который принимает итерируемые объекты и добавляет их к списку. Метод extend()
обычно используется для добавления в список другого списка:
Однако в рассматриваемом коде extend()
принимает не список, а генератор, что значительно оптимизирует программу:
- отпадает необходимость повторного считывания данных;
- не нужно хранить в памяти множество потомков.
Метод extend()
может добавлять в список любые итерируемые объекты – генераторы, строки, кортежи, списки. Это называется утиной типизацией.
Где еще пригодится yield
Использование yield
решает проблему перегрузки памяти при работе с объемными файлами. К примеру, считывание объемных csv-файлов часто приводит к зависанию и прерыванию программы с ошибкой MemoryError
. Чтобы не загружать в память весь файл сразу, и считывать только нужные строки, применяется yield
. Этот код, к примеру, вернет количество строк в файле:
С помощью yield
можно сгенерировать бесконечную (в отличие от range
) последовательность чисел:
Выполнение этого кода будет продолжаться до ручного прерывания.
Заключение
В отличие от return
, который отправляет вызывающей стороне определенное значение, yield
может создавать последовательность значений. Использование yield
целесообразно в тех случаях, когда нужно выполнить итерацию по последовательности значений, но при этом хранить всю последовательность в памяти нежелательно.
Yield
используются в генераторах Python. Функция-генератор определяется как обычная функция, но всякий раз, когда ей нужно выдать значение, она делает это с помощью ключевого слова yield
, а не return
. Если тело def
содержит yield
, функция автоматически становится генераторной.
Материалы по теме
- Как в Python применяются вложенные функции
- Упрощаем разработку: асинхронные функции Python
- Как не быть тем парнем, а писать функции лучше