Генераторы в 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 "<stdin>", line 1, in <module> 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) <class 'list'> >>> 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 <generator object <genexpr> at 0x7fa586339f10> >>> type(a) <class 'generator'> >>> next(a) 1 >>> next(a) 2
Пример со звездочкой с помощью генераторного выражения будет выглядеть так:
>>> d = ('*' for i in range(5)) >>> for i in d: ... print(i) ... * * * * *
В отличие от генераторных выражений, yield-функции более универсальны не только из-за произвольного количества кода в их теле. В них вы можете передавать разные значения аргументов. А значит, одна и та же функция может использоваться для создания несколько разных генераторов.
Практическая работа
В задании к прошлому уроку требовалось написать класс-итератор, объекты которого генерируют случайные числа в количестве и в диапазоне, которые передаются в конструктор. Напишите выполняющую ту же задачу генераторную функцию. В качестве аргументов она должна принимать количество элементов и диапазон.
Курс с примерами решений практических работ:
pdf-версия