На этом шаге мы рассмотрим назначение и использование таких функций.
Функцией-генератором называется функция, которая может возвращать одно значение из нескольких значений на каждой итерации. Приостановить выполнение функции и превратить функцию в генератор позволяет ключевое слово yield. В качестве примера напишем функцию, которая возводит элементы последовательности в указанную степень:
def func(x, y): for i in range(1, x + 1): yield i ** y for n in func(10, 2): print(n, end=" ") # Выведет: 1 4 9 16 25 36 49 64 81 100 print() # Вставляем пустую строку for n in func(10, 3): print(n, end=" ") # Выведет: 1 8 27 64 125 216 343 512 729 1000
Функции-генераторы поддерживают метод __next__(), который позволяет получить следующее значение. Когда значения заканчиваются, метод возбуждает исключение StopIteration. Вызов метода __next__() в цикле for производится незаметно для нас.
В качестве примера перепишем предыдущую программу и используем метод __next__() вместо цикла for:
def func(x, y): for i in range(1, x + 1): yield i ** y i = func(3, 3) print(i.__next__()) # Выведет: 1 (1 ** 3) print(i.__next__()) # Выведет: 8 (2 ** 3) print(i.__next__()) # Выведет: 27 (3 ** 3) print(i.__next__()) # Исключение StopIteration
Получается, что с помощью обычных функций мы можем вернуть все значения сразу в виде списка, а с помощью функций-генераторов - только одно значение за раз. Эта особенность очень полезна при обработке большого количества значений, т. к. не понадобится загружать весь список со значениями в память.
В Python 3.3 появилась возможность вызвать одну функцию-генератор из другой. Для этого применяется расширенный синтаксис ключевого слова yield:
yield from <Вызываемая функция-генератор>
Рассмотрим следующий пример. Пусть у нас есть список чисел, и нам требуется получить другой список, включающий числа в диапазоне от 1 до каждого из чисел в первом списке. Чтобы создать такой список, мы напишем код. показанный ниже:
def gen(l): for e in l: yield from range(1, e + 1) l = [5, 10] for i in gen([5, 10]): print(i, end = " ")
Здесь мы в функции-генераторе gen перебираем переданный ей в качестве параметра список и для каждого его элемента вызываем другую функцию-генератор. В качестве последней выступает выражение, создающее диапазон от 1 до значения этого элемента, увеличенного на единицу (чтобы это значение вошло в диапазон). В результате на выходе мы получим вполне ожидаемый результат:
1 2 3 4 5 1 2 3 4 5 6 7 8 9 10
Усложним задачу, включив в результирующий список числа, умноженные на 2. Код, выполняющий эту задачу, показан ниже:
def gen2(n): for e in range(1, n + 1): yield e * 2 def gen(l): for e in l: yield from gen2(e) l = [5, 10] for i in gen([5, 10]): print(i, end = " ")
Здесь мы вызываем из функции-генератора gen написанную нами самими функцию-генератор gen2. Она создает диапазон, перебирает все входящие в него числа и возвращает их умноженными на 2. Результат работы приведенного в листинге кода таков:
2 4 6 8 10 2 4 6 8 10 12 14 16 18 20
На следующем шаге мы рассмотрим декораторы функций.