Что такое yield
Это оператор для возврата из функции с «заморозкой» состояния ее локальной переменной. При выполнении цикла итератор доходит до yield, возвращает значение и останавливается до следующего вызова.
Его удобно использовать в разных сценариях, например: при проверке большого объема данных, поиске и выводе записей, соответствующих определенным критериям. С помощью yield не придется повторно проверять уже обработанные данные, так как после выполнения нужной операции программа вернется к тому месту, на котором остановилась.
Таким образом, добавление этого ключевого слова в код Python помогает экономить оперативную память и повышает производительность программы.
Как работает yield
Для понимания принципа работы оператора нужно хорошо знать базовые понятия Python, такие как «генерация» и «итерация». Поэтому начнем с основ.
При работе со списком все входящие в него элементы можно перебирать последовательно, один за другим. Этот процесс называется итерацией.
Например, код
list1 = [10, 11, 12]
for i in list1 :
print(i)
выведет последовательность:
10
11
12
Здесь list1 — итерируемый объект. Итерируемыми могут быть не только списки (list), но и строки, файлы — всё, для чего можно использовать for.
Итератор создается во время генерации list1 с помощью спискового включения:
list1 = [x*5 for x in range(3)]
for i in list1 :
print(i)
Этот код выполняет следующие шаги:
- создание списка list1, в котором:
- range(3) создает последовательность трех чисел от 0 до 2;
- x*5 умножает каждое число последовательности на 5, результаты вносятся в список;
- вывод: for проходит по каждому элементу и выполняет print.
Результат выполнения кода:
0
5
10
Таким образом, эта программа на Python сначала создает последовательность из первых трех чисел, умноженных на 5, а затем выводит каждый элемент на экран.
Удобство итерируемых объектов — в отсутствии ограничений на количество повторных считываний. Недостаток в том, что все данные хранятся в оперативной памяти. Это ведет к снижению производительности приложения, особенно при обработке больших объемов информации.
Для решения проблемы используются генераторы. Это итерируемые объекты, данные из которых возможно считывать только по одному разу. Элементы генерируются в процессе выполнения кода и не хранятся в памяти.
Вот как можно реализовать тот же код:
generator1 = (x*5 for x in range(3))
for i in generator1:
print(i)
Как работает код:
- создание generator1 по той же логике:
- range(3) создает последовательность трех чисел от 0 до 2;
- x*5 умножает каждое число последовательности на 5;
- вывод: цикл for проходит по каждому элементу generator1 и выполняет print.
Результат выполнения кода:
0
5
10
Каждый раз, когда происходит итерация for, генерируется новое значение, пока не исчерпается диапазон чисел, заданный range(). Генератор не сразу вычисляет все элементы, он делает это по мере необходимости, а затем «забывает» результаты вычислений.
Оператор yield при работе с list возвращает результат — объект, которым является генератор.
Преобразуем код в нашем примере:
def create_generator():
list1 = range(3)
for i in list1:
yield i*5
generator1 = create_generator()
print(generator1)
for i in generator1:
print(i)
Здесь мы сначала определяем генератор с помощью create_generator(). Внутри этой функции создается переменная list1, которая содержит числа из диапазона range(3). Затем в цикле for для каждого элемента list1 возвращается значение i*5 с помощью оператора yield.
В строке 6 создаем экземпляр генератора. Вызов create_generator() возвращает объект, он сохраняется в переменной generator1.
Строка 7 просто выводит generator1 (сообщает, что это генератор). На экране появится информация о нем, например <generator object create_generator at 0x000001D52A90BAC0>, так как generator1 — это не список значений, а объект.
Строки 8–9 отвечают за итерацию по генератору. Цикл for проходит по каждому элементу, который возвращает генерация. Так как внутри create_generator() было определено yield i*5, при каждой итерации будет возвращаться произведение текущего элемента i на 5.
Результат выполнения кода:
<generator object create_generator at 0x7f4ffa115ff0>
0
5
10
Первая строка — вывод объекта. Остальные строки — результаты умножения каждого элемента list1 на 5.
Рассмотрим другой пример: применим ключевое слово для сохранения состояния при остановке выполнения операции.
Исходный код:
def iterator1():
for i in range (100000000000000):
yield i
list_1, list_2 = [], []
itrtr = iterator1()
for i in range(3):
list_1.append(next(itrtr))
for i in range(3):
list_2.append(next(itrtr))
print(list_1)
print(list_2)
Разберем этот код по шагам, чтобы понять, как он работает.
Строки 1–3 описывают создание iterator1(). В строке for i in range (100000000000000) функция iterator1() создает генератор, который последовательно возвращает целые числа от 0 до 99 999 999 999 999. Ключевое слово в строке 3 позволяет сделать функцию генератором. Теперь она будет не создавать все числа сразу, а генерировать элементы поочередно по мере необходимости, не храня их в памяти.
Строка 4 задает пустые списки list_1 и list_2.
В строке 6 происходит создание экземпляра генератора:
itrtr = iterator1()
Переменная itrtr становится экземпляром генератора, созданного функцией iterator1().
Далее происходит заполнение списков.
Строки 7–8 наполняют list_1:
for i in range(3):
list_1.append(next(itrtr))
В цикле трижды вызывается метод next(), который вызывает следующий сгенерированный элемент. Это приводит к добавлению первых трех значений, полученных при генерации itrtr, в список list_1.
Аналогичным образом наполняется второй пустой список list_2 в строках 9–10:
for i in range(3):
list_2.append(next(itrtr))
Цикл добавляет следующие три элемента, сгенерированные тем же itrtr, в список list_2.
Две последние строки кода выводят результаты с помощью команд print(list_1) и print(list_2).
На этом этапе оба списка содержат по три элемента, которые были получены путем последовательной генерации. Поэтому вывод выглядит так:
[0, 1, 2]
[3, 4, 5]
Рассмотренный код на Python последовательно генерирует большое количество чисел, но вместо того, чтобы хранить все эти числа в памяти, он постепенно извлекает их по мере необходимости. Это особенно полезно для обработки больших наборов данных, где хранение всех элементов в памяти невозможно или неэффективно.
Пример использования в Python
Разберем применение оператора на примере программы, которая выводит числа от 2 до 20 в третьей степени.
def nextCube():
acc = 2
while True:
yield acc**3
acc += 1
count = 1
for num in nextCube():
if count > 19:
break
print(num)
count += 1
На выходе получаем список из 19 чисел, который состоит из чисел от 2 до 20 в третьей степени:
8
27
64
125
216
343
512
729
1000
1331
1728
2197
2744
3375
4096
4913
5832
6859
8000
Поясним логику выполнения кода.
Функция nextCube генерирует бесконечную последовательность чисел в кубе начиная с числа 2:
- строка 2: переменная acc инициализируется со значением 2;
- строка 3: начинается бесконечный цикл while True, который никогда не завершится естественным путем;
- строка 4: с использованием оператора yield функция возвращает куб текущего значения переменной acc (acc**3). После этого выполнение nextCube временно останавливается, и управление передается обратно в вызов;
- строка 5: переменная acc увеличивается на 1.
На ключевом слове функция возвращает следующее значение и останавливается на текущей итерации. После повторного обращения к программе выполнение кода начнется не заново, а с места остановки.
Строки 6–11 запрашивают и выводят сгенерированные 19 чисел:
- строка 6: переменная count инициализируется с 1;
- строка 7: начинается цикл for, который перебирает элементы, возвращаемые nextCube() генератором;
- строка 8: проверяется условие выхода из for; если count больше 19, выполняется break, и цикл завершается;
- строка 10: текущее значение куба, полученное от генератора, выводится на печать;
- строка 11: переменная count увеличивается на единицу после каждой итерации.
Таким образом, программа эффективно создает и выводит на экран результаты расчетов с помощью механизма последовательной генерации.
Преимущества и недостатки yield
Использование ключевого слова позволяет разработчикам больше не думать о выделении и освобождении памяти. Оператор позволяет реализовать обработку больших объемов данных без потери производительности.
Однако yield делает код более сложным, из-за чего возрастает риск ошибок, увеличивается время на продумывание логики и отладку ПО. С правильным внедрением оператора в код на Python помогают AI-ассистенты — например, GigaCode, доступный на платформе GitVerse.