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

Когда вы вызываете функцию с ключевым словом yield, код в функции не выполняется. Вместо этого создается объект-генератор. Его можно сохранить в переменной. Этот объект-генератор способен запускать код внутри функции по требованию.

При вызове объекта-генератора Python запускает код внутри функции-генератора один раз. Он останавливается при наличии ключевого слова yield и выдает значение коду, который его вызвал.

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

Этот процесс продолжается до тех пор, пока остаются значения для выдачи.

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

Чтобы досконально разобраться в работе ключевого слова yield и генераторов, сперва нужно хорошо усвоить, что такое итераторы и итерируемые объекты. В этом руководстве мы разберем работу ключевого слова yield на высоком уровне, без технических подробностей.

Зачем нужен yield в Python?

Ключевое слово yield полезно, когда вы перебираете большую группу значений. Применение yield вместо return позволяет более эффективно использовать память.

Допустим, у вас есть функция, которая читает текстовый файл и выводит каждое слово в консоль. Чтобы сделать это традиционным способом, вам нужно сохранить слова в списке и перебирать их в цикле, верно?

Но что, если в файле около миллиарда слов?

Программа на Python не сможет обработать список из миллиарда элементов. То есть хранение их в списке отпадает. Чтобы справиться с задачей, вам нужен механизм, позволяющий перебирать слова и при этом не хранить их в списке.

Именно здесь вы можете использовать генераторы, то есть функции, которые выдают значения.

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

Генератор сохраняет в памяти только текущее слово. Кроме того, он знает, как получить следующее. При таком подходе генератор может просмотреть миллиард слов, не испытывая проблем с потреблением памяти.

Теоретически вы можете использовать генератор для перебора бесконечного количества слов, поскольку память почти не требуется.

Самое интересное, что синтаксис генератора выглядит идентично применению цикла for…in к списку. То есть, несмотря на то, что механизм совершенно другой, синтаксис остается прежним.

Пример

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

Например, вот функция генератора, которая возводит в квадрат числа из списка:

def square(numbers):
    for n in numbers:
        yield n ** 2

Теперь вызовем эту функцию и выведем результат:

squares = square([1, 2, 3, 4, 5])
print(squares)

Вывод:

<generator object square at 0x7fd8f4dff580>

Вы можете подумать, что этот объект хранит квадраты чисел [1, 2, 3, 4, 5], верно? Но это не так! Объект squares не хранит ни значения, ни их квадраты. Вместо этого объект-генератор дает вам возможность вычислять квадраты по требованию.

Чтобы действительно вычислить квадраты, необходимо вызвать объект-генератор. Один из способов сделать это – использовать встроенную функцию next(). Эта функция просит генератор вычислить следующее значение квадрата.

Давайте вызовем функцию next() пять раз и выведем результаты:

print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))
print(next(squares))

Вывод:

1
4
9
16
25

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

Но откуда он знает, какое значение нужно вычислить?

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

Вся эта история с функцией next() немного запутана, не так ли? Конечно! На самом деле вам не нужно использовать функцию next() для итерации генератора. Вместо этого вы можете использовать старый добрый цикл for.

def square(numbers):
    for n in numbers:
        yield n ** 2

squares = square([1, 2, 3, 4, 5])

for square in squares:
    print(square)

Вывод:

1
4
9
16
25

Обратите внимание, что под капотом цикл for вызывает функцию next() на генераторе. Он делает это до тех пор, пока для генератора не останется значений для вычисления.

Теперь давайте остановимся на том, в чем смысл функций с ключевым словом yield.

Шпаргалка для понимания работы yield

Если генераторы и ключевое слово yield для вас пока новые понятия, эта шпаргалка поможет вам разобраться в том, что делает код, содержащий yield.

Обратите внимание, что этот прием не является эквивалентной заменой оператора yield! Он лишь поможет вам понять, что делает код, в котором используется это ключевое слово.

Когда вы видите ключевое слово yield:

  • Добавьте в качестве первой строки функции строку result = []
  • Замените все выражения yield val на result.append(val)
  • Добавьте return result в конец функции
  • Теперь прочитайте функцию и поймите, что она делает.
  • Сравните эту функцию с исходной.
def square(numbers):
    for n in numbers:
        yield n ** 2

Чтобы увидеть, что делает эта функция, давайте применим к ней описанные выше шаги:

1. Добавьте result = [] в начало функции.

def square(numbers):
    result = []
    for n in numbers:
        yield n ** 2

2. Замените yield val на result.append(val).

def square(numbers):
    result = []
    for n in numbers:
        result.append(n ** 2)

3. Добавьте return result в нижнюю часть функции.

def square(numbers):
    result = []
    for n in numbers:
        result.append(n ** 2)
    return result

4. Прочитайте функцию и поймите, что она делает.

Итак, теперь вы ясно видите, что эта функция берет список чисел, возводит их в квадрат и возвращает список возведенных в квадрат чисел.

Важно понимать, что эта модификация не является заменой функции c yield! Она лишь помогает лучше понять исходный код.

Заключение

Ключевое слово yield – это эффективный с точки зрения памяти способ перебора большой коллекции значений. Функция с ключевым словом yield называется генератором.

Функция-генератор не хранит итерируемые значения в памяти. Вместо этого она заботится о текущем значении и знает, как получить следующее. Это позволяет перебирать огромное количество значений. Память нужна только для одного значения.

При итерации объекта-генератора можно применять традиционный синтаксис цикла for. При этом генератор генерирует значения на ходу.

Перевод статьи Artturi Jalli “Python ‘yield’ Keyword—What Does It Do? [with Examples]”.

4 комментария к “Ключевое слово yield в Python”

  1. Пингбэк: Как разделить список на несколько равных частей - pythonturbo

  2. Пингбэк: Операторы in и not in в Python

  3. Пингбэк: Оператор break в Python

  4. Пингбэк: Итераторы и генераторы в Python

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *