Ключевое слово yield в Python: как оно работает
На профильных форумах разработчиков часто можно встретить вопрос, касающийся слова yield в коде Python. Начинающих программистов интересует, что такое yield, и как оно используется. Давайте рассмотрим этот вопрос на нескольких примерах. Но для начала разберемся с теорией.
Где используется yield в Python
В языке программирования Python с помощью yield в функциях создаются генераторы. Что такое генератор? По своей сути, это особый вид итераторов, благодаря которым функции сохраняют текущее состояние между вызовами и могут поочередно возвращать значения. Итерация — это поочередный вызов и обработка элементов списка. Проще говоря, при обработке генератором object или иной элемент кода будет вызываться при каждом обращении.
Как работает генератор
В отличие от привычных функций, которые используют return для возврата значения и завершения работы, генераторы в Python применяют yield для возврата значения, но при этом они сохраняют своё состояние между вызовами. Если все еще непонятно, тогда загляните в Stackoverflow. Там всегда есть куча примеров, которые помогут вам лучше использовать генератор.
При наличии в теле программы слова yield, функция возвращает значение и приостанавливает свое выполнение. При следующем вызове генератор может итерировать выполнение с той самой строки, где оно было приостановлено. То есть там, где он встретил yield.
Чтобы все это лучше понять, взглянем на данный фрагмент:
def simple_generator(): yield 1 yield 2 yield 3 gen = simple_generator() print(next(gen)) # Вывод: 1 print(next(gen)) # Вывод: 2 print(next(gen)) # Вывод: 3
Здесь мы видим функцию под названием simple_generator. Она поочередно возвращает значения 1, 2 и 3 при каждом вызове next().
Какие преимущества дает yield для Python
Применяя yield в своих приложениях, вы получаете два очевидных преимущества:
- Генераторы обрабатывают итерируемые объекты поочередно, а не загружают всю последовательность чисел сразу и не хранят значения в памяти. То есть, они ее не перегружают.
- Поочередное вычисление элементов более оптимально при работе с большими наборами данных.
Термин yield преобразует функцию в генератор. Его можно применять для итерации с сохранением промежуточных состояний.
Как использовать yield для чтения большого файла построчно в Python
Давайте взглянем на еще один пример использования yield в Python. Благодаря ему мы сможем построчно прочитать большой файл. Практическое преимущество здесь очевидно: итераторы начинают обрабатывать код без необходимости загрузки в память всего файла сразу. Таким образом, итератор не перегружает память.
def read_large_file(file_path): with open(file_path, 'r') as file: for line in file: yield line.strip() # Применение generator file_path = 'large_text_file.txt' for line in read_large_file(file_path): # Обрабатываем каждую строку файла print(line)
Объяснение кода:
Функция read_large_file(file_path) получает путь к файлу и открывает его для чтения. Далее она выполняет построчную итерацию в файле с помощью цикла for. Базовый вариант предполагает, что функция возвращает все строки одновременно. Но здесь она использует yield. Из-за этого функция возвращает только одну строку за раз (без символа новой строки в конце).
Что касается генератора, то он создается при вызове функции read_large_file. Поскольку здесь присутствует цикл for, то каждая строка файла обрабатывается отдельно. Если бы мы имели дело с действительно большим файлом, то мог бы возникнуть риск, что он не помещается в оперативной памяти. Здесь же мы видим, как yield устраняет подобную проблему.
Применяем yield для многозадачной обработки данных
Вот еще один, немного более сложный пример использования yield. Здесь он нам понадобится для многозадачной обработки данных без использования потоков или асинхронного программирования. Применяя это ключевое слово yield, мы выполняем несколько задач одновременно так же эффективно, как и при многопоточности.
В представленном ниже фрагменте мы реализуем диспетчер задач, который будет управлять несколькими процессами с возможностью переключения между ними.
import time def task1(): count = 0 while True: count += 1 print(f"Task 1: {count}") yield # Возвращаем управление диспетчеру задач def task2(): count = 0 while True: count += 1 print(f"Task 2: {count}") yield # Возвращаем управление диспетчеру задач def task3(): count = 0 while True: count += 1 print(f"Task 3: {count}") yield # Возвращаем управление диспетчеру задач def task_scheduler(tasks, max_cycles): # Инициализация генераторов задач task_generators = [task() for task in tasks] for _ in range(max_cycles): for task_gen in task_generators: next(task_gen) # Переключаемся между задачами time.sleep(0.1) # Задержка для демонстрации параллельной работы # Пример использования tasks = [task1, task2, task3] task_scheduler(tasks, max_cycles=5)
Как вы заметили, каждая из функций: task1, task2 и task3, представляет собой бесконечный цикл работы и вывода значений счетчика. Термин yield нужен для временной приостановки выполнения задачи и возвращения управления диспетчеру задач.
Что касается функции task_scheduler(tasks, max_cycles), то она представляет собой диспетчер задач, который инициализирует генераторы для каждой задачи и поочередно переключается между итерируемыми объектами, вызывая next(task_gen) для каждого генератора. Для демонстрации паузы между переключениями задач, имитируя асинхронную обработку, вставлен time.sleep(0.1).
В списке tasks мы видим три задания: task1, task2 и task3. Диспетчер делает 5 циклов переключений, последовательно переключая управление на следующее задание. В итоге перед нами возникает генераторное выражение с поочередным значением счетчиков каждого задания, что создает видимость параллельной работы.
Фрагмент, который вы просмотрели, может применяться, например, в компьютерной игре, где требуется одновременно управлять несколькими персонажами, или в программе, где несколько задач должны выполняться по очереди с высокой частотой переключений.
Заключение
Применение yield дает Python-программисту несколько заметных преимуществ. Во-первых, программа меньше пожирает памяти. Вместо загрузки всего файла в память, генератор обрабатывает строки поочередно, что дает немалую экономию ресурсов. Во-вторых, последовательная обработка строк способна ускорить выполнение программы. Это особенно важно, когда нужно обработать крупный файл или потоковые данные. И, наконец, третье преимущество. Документация приложения, в котором применяется этот термин, выглядит лаконично и интуитивно понятно, чем аналогичный экземпляр с явным управлением состоянием и использованием внешних итераторов.
Применять yield рекомендуется при работе над проектами, где требуется чтение или обработка очень больших файлов: логов действий пользователей, данных о транзакциях и похожих систем.
Сообщить об опечатке
Текст, который будет отправлен нашим редакторам: