Python предоставляет программисту большой набор инструментов, один из которых — yield. Он заменяет обычный возврат значений из функции и позволяет сэкономить память при обработке большого объема данных.
yield – один из тех инструментов, использовать которые вовсе не обязательно. Всё, что можно реализовать с его помощью, можно сделать, используя обычный возврат return. Однако этот оператор позволяет не только сэкономить память, но и реализовать взаимодействие между несколькими последовательностями в пределах одного цикла.
Что такое yield и как это работает
Yield – ключевое слово, которое используется вместо return. С его помощью функция возвращает значение без уничтожения локальных переменных, кроме того, при каждом последующем вызове функция начинает своё выполнение с оператора yield.
Функция, содержащая yield в Python 3, называется генератором. Чтобы разобраться, как работает yield и зачем его используют, необходимо узнать, что такое генераторы, итераторы и итерации.
Но перед этим рассмотрим пример:
def numbers_range(n): for i in range(n): yield i a = numbers_range(4) print(type(a)) for b in a: print(b) # Выведено в консоль будет: <class 'generator'> 0 1 2 3
Тип полученного значения при вызове функции — это генератор. Один из способов получения значений из генератора — это их перебрать в цикле for. Им мы и воспользовались. Но можно его легко привести к списку, как мы сделали в статье про числа Фибоначчи.
Теперь разберемся, как это всё работает.
Что такое итерации
В программировании итерация — это процесс, в котором последовательно повторяется набор инструкций определенное количество раз или до тех пор, пока не будет выполнено условие.
Цикл — это повторяющаяся последовательность команд, каждый цикл состоит из итераций. То есть, одно выполнение цикла — это итерация. Например, если тело цикла выполнилось 5 раз, это значит, что прошло 5 итераций.
Итератор — это объект, позволяющий «обходить» элементы последовательностей. Программист может создать свой итератор, однако в этом нет необходимости, интерпретатор Python делает это сам.
Что такое генераторы
Генератор — это обычная функция, которая при каждом своём вызове возвращает объект. При этом в функции-генераторе вызывается next.
Отличие генераторов от обычной функции состоит в том, что функция возвращает только одно значение с помощью ключевого слова return, а генератор возвращает новый объект при каждом вызове с помощью yield. По сути генератор ведет себя как итератор, что позволяет использовать его в цикле for.
Программист может не использовать генераторы, однако в некоторых ситуациях оптимизировать программу можно только с их помощью.
Помимо yield, есть и другие способы создания генераторов, они описаны в статье о генераторах списка.
Функция next()
Эта функция позволяет извлекать следующий объект из итератора. То есть чтобы цикл перешел с текущей итерации на следующую, вызывается функция next(). Когда в итераторе заканчиваются элементы, возвращается значение, заданное по умолчанию, или возбуждается исключение StopItered.
На самом деле каждый объект имеет встроенный метод __next__, который и обеспечивает обход элементов в цикле, а функция next() просто вызывает его.
Функция имеет простой синтаксис: next(итератор[,значение по умолчанию])
. Она автоматически вызывается интерпретатором Python в циклах while и for.
Вот пример использования next:
def numbers_range(n): for i in range(n): yield i a = numbers_range(4) print(next(a)) print(next(a)) print(next(a)) print(next(a)) # Будет выведено в консоль 0 1 2 3
Преимущества использования yield
yield используют не потому, что это определено синтаксисом Python, ведь всё, что можно реализовать с его помощью, можно реализовать и с помощью обычного return.
Функция, которая обрабатывает большую последовательность и использует обычный return, требует от интерпретатора выделять ей много памяти. И если обычно такие функции не сильно влияют на производительность программы, то в проектах, содержащих последовательности с миллионами элементов, они потребляют очень много памяти.
Использование yield в языке программирования Python 3 позволяет не сохранять в память всю последовательность, а просто генерирует объект при каждом вызове функции. Это позволяет обойтись без использования большого количества оперативной памяти.
Сравнение производительности return и yield
Часто yield используют, когда необходимо прочитать большой текстовый файл. Чтобы наглядно показать преимущество использования генераторов, нужно создать два скрипта:
- Первый использует обычный return, он читает все строки файла и заносит их в список, а затем выводит все строки в консоли.
- Второй использует 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 def numbers_range(n): for i in range(n): yield i #yield from def numbers_range(n): yield from range(n)
Однако истинное предназначение нововведения немного в другом. Конструкция позволяет «вкладывать» один генератор в другой, то есть создавать субгенераторы.
yield from позволяет программисту легко управлять сразу несколькими генераторами, настраивать их взаимодействие и, конечно, заменить более длинную конструкцию for+yield, например:
def subgenerator(): yield 'World' def generator(): yield 'Hello' yield from subgenerator() #Запрашиваем значение из субгенератора yield '!' for i in generator(): print(i, end = ' ') # Вывод Hello World !
Как видно из примера, yield from позволяет одному генератору получать значения из другого. Этот инструмент сильно упрощает жизнь программиста, особенно при асинхронном программировании.
Заключение
Использование генераторов в правильных местах позволяет значительно уменьшить потребление памяти, кроме того, взаимодействие с генераторами более прозрачно и легче поддается отладке.
yield – это лишь одно из многих полезных средств языка Python, которое может быть без проблем заменено обычным возвратом из функции с помощью return. Оно добавлено в язык, чтобы оптимизировать производительность программы, упростить код и его отладку и дать программистам возможность применять необычные решения в специализированных проектах.