Генераторы в Python. Оператор yield. Генераторные выражения
Генераторы можно считать подвидом итераторов, а способ их создания – инструментом для создания несложных итераторов.
В отличие от обычных итераторов, генераторы создаются путем вызова функции, а не от класса.
Чтобы функция возвращала объект-генератор, в ее теле должен быть оператор yield. Когда любая yield-содержащая функция вызывается, она возвращает объект типа generator, а не None или какой-нибудь другой тип данных через оператор return .
У генераторов методы __next__ и __iter__ создаются средствами самого языка, то есть автоматически. Программисту их определять не надо, что упрощает создание пользовательских типов итераторов.
>>> def starmaker(n): . while n > 0: . yield '*' . n -= 1 . >>> type(starmaker) >class 'function'> >>> s = starmaker(3) >>> type(s) >class 'generator'> >>> next(s) '*' >>> next(s) '*' >>> next(s) '*' >>> next(s) Traceback (most recent call last): File ">stdin>", line 1, in >module> StopIteration
В определенном смысле оператор yield заменяет return с тем исключением, что мы снова возвращаемся в функцию, когда вызывается next() . При этом объект-генератор помнит состояние переменных и место, откуда при прошлом вызове произошел выход из функции.
Если мы сделаем нечто подобное
>>> def g(): . yield 1 .
то не получим бесконечный генератор, потому что код тела функции полностью выполнится при первом вызове next() :
>>> a = g() >>> next(a) 1 >>> next(a) Traceback (most recent call last): File "", line 1, in StopIteration
Обратите внимание, что функция starmaker делает то же самое, что класс, описанный в прошлом уроке:
>>> class A: . def __init__(self, qty): . self.qty = qty . def __iter__(self): . return self . def __next__(self): . if self.qty > 0: . self.qty -= 1 . return '+' . else: . raise StopIteration . >>> a = A(3) >>> for i in a: . print(i) . + + +
При этом код функции, создающей итератор, намного короче аналогичного класса. Поэтому классы-итераторы скорее уместны, когда создаются сложные объекты, включающие множество полей и сложную логику их обработки, а не только методы __iter__ и __next__ .
Генераторные выражения
Существует еще более простой, чем функция с yield , способ создания итераторов – генераторные выражения. Они подходят, когда код тела функции можно записать в одну строку.
Синтаксис генераторных выражений подобен генераторам списков, рассматриваемых в курсе «Python. Введение в программирование». Однако, в отличие от списков, в случае генераторов используются круглые скобки.
Напомним, как выглядят генераторы списков и то, что возвращают они списковый тип данных:
>>> a = [i+1 for i in range(10)] >>> a [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] >>> type(a) >>> import random >>> b = [random.randint(0,9) for i in range(5)] >>> b [2, 5, 5, 2, 9] >>> c = [i for i in b if i % 2 == 0] >>> c [2, 2]
Результат выражения, стоящего до for , добавляется на каждой итерации цикла в итоговый список. Выполнение выражения генератора списка сразу заполняет список.
В случае генераторных выражений создается объект-генератор, у которого будет вычисляться очередной элемент только при каждом вызове next() :
>>> a = (i+1 for i in range(10)) >>> a at 0x7fa586339f10> >>> type(a) >>> next(a) 1 >>> next(a) 2
Пример со звездочкой с помощью генераторного выражения будет выглядеть так:
>>> d = ('*' for i in range(5)) >>> for i in d: . print(i) . * * * * *
В отличие от генераторных выражений, yield-функции более универсальны не только из-за произвольного количества кода в их теле. В них вы можете передавать разные значения аргументов. А значит, одна и та же функция может использоваться для создания несколько разных генераторов.
Практическая работа
В задании к прошлому уроку требовалось написать класс-итератор, объекты которого генерируют случайные числа в количестве и в диапазоне, которые передаются в конструктор. Напишите выполняющую ту же задачу генераторную функцию. В качестве аргументов она должна принимать количество элементов и диапазон.
Курс с примерами решений практических работ:
pdf-версия
X Скрыть Наверх
Объектно-ориентированное программирование на Python
Что такое yield в python
Ключевое слово yield в Python: для чего необходимо использовать
28 марта 2023
Оценки статьи
Еще никто не оценил статью
В Python ключевое слово yield используется для создания генераторов. Генераторы являются специальным типом итераторов, которые позволяют вам лениво генерировать значения при каждой итерации.
В этой статье мы рассмотрим, что такое ключевое слово yield и как его использовать для создания генераторов.
Создание генераторов с помощью ключевого слова yield в Python
Ключевое слово yield используется для определения генераторной функции. Генераторная функция — это функция, которая возвращает объект-генератор, когда она вызывается. В отличие от обычных функций, которые возвращают значение и завершаются, генераторная функция может приостанавливаться и возобновлять свою работу во время выполнения.
Вот простой пример генераторной функции, которая генерирует последовательность чисел:
def my_generator(): yield 1 yield 2 yield 3
Обратите внимание, что в этой функции используется ключевое слово yield , а не return . Когда функция вызывается, она возвращает объект-генератор, а не список значений. Каждый вызов метода next() на объекте-генераторе приводит к выполнению кода до следующей инструкции yield , после чего выполнение функции приостанавливается и значение возвращается в качестве результата.
gen = my_generator() print(next(gen)) # 1 print(next(gen)) # 2 print(next(gen)) # 3
Примеры использования ключевого слова yield
1. Генерация бесконечной последовательности
Одним из преимуществ генераторов является возможность генерировать бесконечные последовательности значений без необходимости создавать большие списки в памяти. Вот пример генераторной функции, которая генерирует бесконечную последовательность простых чисел:
def primes(): yield 2 primes_list = [2] num = 3 while True: is_prime = True for prime in primes_list: if num % prime == 0: is_prime = False break if is_prime: yield num primes_list.append(num) num += 2
Эта функция использует алгоритм «Решето Эратосфена«, чтобы генерировать последовательность простых чисел. Она начинает с генерации числа 2, а затем использует список уже сгенерированных простых чисел для проверки следующего числа на простоту.
2. Применение yield в качестве корутины
Кроме того, yield также может использоваться для создания корутин (coroutine) — функций, которые позволяют временно приостановить выполнение кода и вернуть управление обратно к вызывающей стороне, чтобы та могла продолжить свою работу. Корутины могут быть использованы для решения различных задач, таких как обработка событий, многопоточность и асинхронное программирование.
Вот пример использования yield для реализации корутины:
def coroutine_example(): while True: x = yield print('Received:', x) c = coroutine_example() next(c) # запустить корутину c.send('Hello, world!') # отправить значение в корутину
В этом примере функция coroutine_example создает бесконечный цикл, который ожидает получения значения при помощи yield . Когда значение было отправлено в корутину при помощи метода send , выполнение кода продолжается и выводится сообщение о полученном значении.
Заключение
Ключевое слово yield является мощным инструментом в Python, который позволяет упростить решение различных задач. В статье мы рассмотрели основные способы использования yield в Python, включая создание генераторов, управление исключениями, реализацию итераторов, сборку мусора и создание корутин.
Надеемся, что данная статья помогла вам лучше понять возможности ключевого слова yield в Python и использовать его в своих проектах.
Меню категорий
-
Загрузка категорий.
Функция-генератор. Оператор yield
На этом занятии мы с вами поговорим о функциях-генераторах. Но вначале вернемся к выражениям-генераторам, которые рассматривали на предыдущем занятии. Давайте предположим, что нам нужны средние арифметические значения для разных последовательностей целых чисел:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
2, 3, 4, 5, 6, 7, 8, 9, 10
3, 4, 5, 6, 7, 8, 9, 10
4, 5, 6, 7, 8, 9, 10
5, 6, 7, 8, 9, 10
6, 7, 8, 9, 10
7, 8, 9, 10
8, 9, 10
9, 10
Для этого мы могли бы записать следующее выражение-генератор:
N = 10 a = (sum(range(i, N+1))/len(range(i, N+1)) for i in range(N))
Но оно не очень удобно для восприятия и редактирования и, кроме того, здесь дважды записана функция range() при вычислении среднего значения. Поправить это можно двумя способами. В первом, объявить собственную функцию для вычисления среднего арифметического и вызвать ее в генераторе:
def avg(start, stop, step=1): a = range(start, stop, step) return sum(a) / len(a) N = 10 a = (avg(i, N + 1) for i in range(1, N)) print(list(a))
А во втором способе – создать собственную функцию-генератор, которая бы на выходе выдавала нужные значения. Давайте для начала запишем простую функцию-генератор, а потом вернемся к нашей исходной задаче. Функция будет просто возвращать значения списка:
def get_list(): for x in [1, 2, 3, 4]: yield x
Смотрите, здесь в цикле записан новый для нас оператор yield, который возвращает текущее значение x и «замораживает» состояние функции до следующего обращения к ней (в том числе и все локальные переменные). Именно так определяются функции-генераторы. Если мы сейчас ее вызовем:
d = get_list() print(d)
то, смотрите, переменная d ссылается на объект-генератор, то есть, мы здесь имеем дело с генератором, значения которого можно перебирать с помощью функции next():
print(next(d)) print(next(d))
d = get_list() for i in d: print(i, end=" ")
В этом и заключается роль оператора yield. Он превращает обычную функцию в генератор и при каждом вызове функции next() активизируется функция-генератор, возвращает очередное значение и «замораживает» свое состояние вместе с локальными переменными до следующего вызова функции next(). (Показываем это в режиме отладки).
Надеюсь, вы теперь хорошо представляете, как работает оператор yield и простая функция-генератор. Давайте вернемся к нашей исходной задаче и перепишем функцию avg() с использованием оператора yield (создаем еще одну):
def avg_gen(N, step=1): for i in range(1, N): a = range(i, N+1, step) yield sum(a) / len(a)
Принцип здесь тот же самый, на каждой итерации цикла будет возвращаться новое вычисленное среднее арифметическое значение. Далее, мы можем воспользоваться этой функцией-генератором и отобразить все ее значения, используя функцию list():
b = avg_gen(N) print(list(b))
Как видите, результат полностью совпадает с первоначальным. То есть, мы заменили выражение-генератор на функцию-генератор. Ну и, как всегда, возникает вопрос, зачем это все было надо? Почему бы не пользоваться обычными генераторами? Что нам мешает это делать? В целом, ничего. Преимущество здесь, главным образом, в удобстве использования. В выражении генератора мы можем записать лишь один оператор для формирования значения, а в функции-генераторе – произвольный фрагмент программы, реализующий нужную нам логику формирования очередного значения. В этом ключевое отличие функции-генератора от обычного генератора.
В заключение этого занятия приведу еще один пример использования функции-генератора. Предположим, что мы хотим найти все начальные индексы слова «генератор» в текстовом файле lesson_54.txt. Для этого вначале откроем этот файл на чтение:
try: with open("lesson_54.txt", encoding="utf-8") as file: a = find_word(file, "генератор") print(list(a)) except FileNotFoundError: print("Файл не найден!") except: print("Ошибка обработки файла!")
И внутри блока try вызовем функцию-генератор, которая и будет последовательно возвращать индексы найденного слова «генератор». Саму функцию определим выше (по программе), следующим образом:
def find_word(f, word): g_indx = 0 for line in f: indx = 0 while(indx != -1): indx = line.find(word, indx) if indx > -1: yield g_indx + indx indx += 1 g_indx += len(line)
Мы здесь в первом цикле читаем файл по строкам. Во втором вложенном цикле while ищем указанное слово в строке, используя метод find(). И, если этот метод находит заданный фрагмент, то есть, возвращает значение больше -1, то функция генерирует на выходе значение индекса найденного слова как g_indx + indx. Здесь g_indx – это смещение по тексту для текущей строки, то есть, в ней мы суммируем длины предыдущих строк, чтобы сформировать индекс слова в тексте, а не в строке.
После запуска этой программы видим все найденные индексы данного слова по тексту. Как видите, мы в функции описали нетривиальную логику алгоритма поиска слова в текстовом файле. Сделать это с помощью обычного генератора было бы сложнее. В этом и преимущество функций-генераторов – они позволяют сразу, в одном месте кода, описывать нужный нам функционал.
На этом мы завершим с вами очередное занятие. Для закрепления материала, как всегда, пройдите практические задания и переходите к следующему уроку.
Видео по теме

#1. Первое знакомство с Python Установка на компьютер

#2. Варианты исполнения команд. Переходим в PyCharm

#3. Переменные, оператор присваивания, функции type и id

#4. Числовые типы, арифметические операции

#5. Математические функции и работа с модулем math

#6. Функции print() и input(). Преобразование строк в числа int() и float()

#7. Логический тип bool. Операторы сравнения и операторы and, or, not

#8. Введение в строки. Базовые операции над строками

#9. Знакомство с индексами и срезами строк

#10. Основные методы строк

#11. Спецсимволы, экранирование символов, row-строки

#12. Форматирование строк: метод format и F-строки

#13. Списки — операторы и функции работы с ними

#14. Срезы списков и сравнение списков

#15. Основные методы списков

#16. Вложенные списки, многомерные списки

#17. Условный оператор if. Конструкция if-else

#18. Вложенные условия и множественный выбор. Конструкция if-elif-else

#19. Тернарный условный оператор. Вложенное тернарное условие

#20. Оператор цикла while

#21. Операторы циклов break, continue и else

#22. Оператор цикла for. Функция range()

#23. Примеры работы оператора цикла for. Функция enumerate()

#24. Итератор и итерируемые объекты. Функции iter() и next()

#25. Вложенные циклы. Примеры задач с вложенными циклами

#26. Треугольник Паскаля как пример работы вложенных циклов

#27. Генераторы списков (List comprehensions)

#28. Вложенные генераторы списков

#29. Введение в словари (dict). Базовые операции над словарями

#30. Методы словаря, перебор элементов словаря в цикле

#31. Кортежи (tuple) и их методы

#32. Множества (set) и их методы

#33. Операции над множествами, сравнение множеств

#34. Генераторы множеств и генераторы словарей

#35. Функции: первое знакомство, определение def и их вызов

#36. Оператор return в функциях. Функциональное программирование

#37. Алгоритм Евклида для нахождения НОД

#38. Именованные аргументы. Фактические и формальные параметры

#39. Функции с произвольным числом параметров *args и **kwargs

#40. Операторы * и ** для упаковки и распаковки коллекций

#41. Рекурсивные функции

#42. Анонимные (lambda) функции

#43. Области видимости переменных. Ключевые слова global и nonlocal

#44. Замыкания в Python

#45. Введение в декораторы функций

#46. Декораторы с параметрами. Сохранение свойств декорируемых функций

#47. Импорт стандартных модулей. Команды import и from

#48. Импорт собственных модулей

#49. Установка сторонних модулей (pip install). Пакетная установка

#50. Пакеты (package) в Python. Вложенные пакеты

#51. Функция open. Чтение данных из файла

#52. Исключение FileNotFoundError и менеджер контекста (with) для файлов

#53. Запись данных в файл в текстовом и бинарном режимах

#54. Выражения генераторы

#55. Функция-генератор. Оператор yield

#56. Функция map. Примеры ее использования

#57. Функция filter для отбора значений итерируемых объектов

#58. Функция zip. Примеры использования

#59. Сортировка с помощью метода sort и функции sorted

#60. Аргумент key для сортировки коллекций по ключу

#61. Функции isinstance и type для проверки типов данных

#62. Функции all и any. Примеры их использования

#63. Расширенное представление чисел. Системы счисления

#64. Битовые операции И, ИЛИ, НЕ, XOR. Сдвиговые операторы

#65. Модуль random стандартной библиотеки

#66. Аннотация базовыми типами

#67. Аннотации типов коллекций

#68. Аннотации типов на уровне классов

#69. Конструкция match/case. Первое знакомство

#70. Конструкция match/case с кортежами и списками

#71. Конструкция match/case со словарями и множествами

#72. Конструкция match/case. Примеры и особенности использования
© 2023 Частичное или полное копирование информации с данного сайта для распространения на других ресурсах, в том числе и бумажных, строго запрещено. Все тексты и изображения являются собственностью сайта
Использование ключевого слова «yield» в Python
Одной из особенностей языка программирования Python является ключевое слово yield , которое используется в определении функций и возвращает итератор, по которому можно проходить в цикле.
Чтобы понять как работает yield , рассмотрим пример функции, которая генерирует числа Фибоначчи:
def fibonacci(n): a, b = 0, 1 for _ in range(n): yield a a, b = b, a + b
В этом случае, функция fibonacci является генератором, который возвращает итератор. Каждый раз, когда функция встречает ключевое слово yield , она возвращает следующее значение и «замораживает» свое состояние. При следующем вызове функции, она продолжает работу с того места, где остановилась в прошлый раз, а не начинает все сначала.
for num in fibonacci(10): print(num)
Код выше выведет первые 10 чисел Фибоначчи. Основное преимущество использования yield вместо обычных функций заключается в экономии памяти, так как значения генерируются по одному, а не все сразу. Это особенно полезно при работе с большими объемами данных.
Таким образом, ключевое слово yield в Python используется для создания функций-генераторов, которые возвращают итератор. Это позволяет экономить память и упрощает работу с последовательностями данных.
