Шаг 119.
Основы языка Python.
Пользовательские функции. Функции-генераторы

    На этом шаге мы рассмотрим назначение и использование таких функций.

    Функцией-генератором называется функция, которая может возвращать одно значение из нескольких значений на каждой итерации. Приостановить выполнение функции и превратить функцию в генератор позволяет ключевое слово 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
Что и требовалось нам получить.

    На следующем шаге мы рассмотрим декораторы функций.




Предыдущий шаг Содержание Следующий шаг