Как работает генератор yield в Python. Генераторы для начинающих

Сегодня мы поговорим о том, как в Python создаются функции генераторов и выражения. Вы также получите подробные сведения о том, зачем они вообще нужны и как их использовать в приложениях, написанных на этом языке программирования. Мы также узнаем, в чем основные отличия от обычных итераторов и функций.

Также мы поговорим о том, что такое инструкция yield, являющаяся частью генератора и заменяющая ключевое слово return. Когда программа достигает строки с использованием yield, функция переходит в состояние ожидания и продолжает работать с того места, где она была вызвана снова.

Что такое генератор в Python?

Под генератором в Python подразумевается специальная функция, которая дает возможность временно остановить либо продолжить работу. Генератором возвращается итератор, который используется для того, чтобы проходить код шаг за шагом, получая последовательно каждое значение

С помощью генератора пользователь получает возможность создавать итераторы, решая трудную ситуацию. Дело в том, что создание итератора в Python – это довольно непростая операция, которая требует не только написания класса, но и реализации методов __iter__() и __next__(). А затем нужно задать настройки внутренних состояний и вызывать исключение StopIteration, когда уже больше ничего не требуется возвращать.

Как создавать генератор в Python?

Генератор – это еще один вариант, с помощью которого можно возвращать итераторы, который, к тому же, значительно проще в использовании во время разработки программы. Чтобы его создать, усилий потребуется не больше, чем объявить стандартную функцию.

Есть 2 легких способа создания генераторов. 

Специализированная функция

Создание генератора осуществляется таким же образом, как и обычной функции, но с одним отличием. Вместо return применяется yield. С ее помощью интерпретатор Python получает информацию о том, что это генератор и возвращает итератор. 

Синтаксис следующий. 

def gen_func(args):

    ...

    while [cond]:

        ...

        yield [value]

Return – это последняя инструкция, которая выполняется в ходе вызова функции. А вот Yield временно останавливает исполнение, сохраняет состояние и затем может продолжить работу через некоторое время.

Далее приведем легкий для понимания пример функции генератора Python, определяющей такое значение в последовательности Фибоначчи. 

Работает это таким образом. 

def fibonacci(xterms):

    # первые два условия

    x1 = 0

    x2 = 1

    count = 0




    if xterms <= 0:

        print("Укажите целое число больше 0")

    elif xterms == 1:

        print("Последовательность Фибоначчи до", xterms, ":")

        print(x1)

    else:

        while count < xterms:

            xth = x1 + x2

            x1 = x2

           x2 = xth

           count += 1

           yield xth




fib = fibonacci(5)




print(next(fib))

print(next(fib))

print(next(fib))

print(next(fib))

print(next(fib))

print(next(fib))

Здесь у функции генератора есть цикл while, определяющий следующее значение в последовательности Фибоначчи. Инструкция yield – это часть цикла. После того, как будет объявлена функция генератора, необходимо выполнить ее вызов, передав в качестве аргумента 5. Она вернет исключительно итератор.

Исполнение функции не будет осуществляться до вызова метода next() с вернувшимся объектом в качестве аргумента (то бишь, fib). Для итерации необходимо все шаги повторить шесть раз.

Первые пять вызовов next() были успешными, и они возвращали соответствующий элемент последовательности чисел Фибоначчи. А вот последним было возвращено исключение StopIteration, так как больше не осталось элементов, которые можно было бы вернуть.

После выполнения вывод будет следующим. 

1

2

3

5

8

Traceback (most recent call last):

File «C:/Python/Python3/python_generator.py», line 29, in

print(next(fib))

StopIteration

Выражение генератора

В функционал Python входит написание выражений генератора для создания анонимных функций генератора. В чем-то это напоминает использование лямбда-функций для создания анонимных функций. 

Синтаксис напоминает использование цикла for для создания списков. Единственная разница в том, что здесь используются круглые скобки, а не квадратные. 

# Синтаксис выражения генератора



gen_expr = (var**(1/2) for var in seq)

Еще одна разница между «list comprehension» и «выражением генератора» в том, что при создании списков возвращается целый список, а в случае с генераторами — лишь одно значение за раз.

Давайте приведем пример использования выражения генератора в Python. 

# Создаем список

alist = [4, 16, 64, 256]




# Вычислим квадратный корень, используя генерацию списка

out = [a**(1/2) for a in alist]

print(out)




# Используем выражение генератора, чтобы вычислить квадратный корень

out = (a**(1/2) for a in alist)

print(out)

print(next(out))

print(next(out))

print(next(out))

print(next(out))

print(next(out))

В приведенном выше примере out возвращает список со значениями, возведенными в квадрат. Проще говоря, готовый результат выдается сразу.

Выражение генератора возвращает итератор, который будет выдавать по одному значению за раз. В списке у нас 4 элемента. Проще говоря, четыре последовательных вызова метода next() напечатают квадратные корни соответствующих элементов списка.

Но, так как вызов этого метода осуществлялся 5 раз, то также возвращается исключение StopIteration

[2.00, 4.0, 8.00, 16.0]

at 0x000000000359E308>

2.0

4.0

8.0

16.0

Traceback (most recent call last):

File «C:/Python/Python3/python_generator.py», line 17, in

print(next(out))

StopIteration

Особенности использования генератора

Теперь давайте рассмотрим то, как использовать генератор в приложениях, написанных на Python, на практике. Ранее метод next() использовался по отношению к итератору, который возвращала функция генератора.

С использованием метода next()

Метод next() – наиболее распространенный вариант, как можно получить значения из функции генератора. После вызова этого метода происходит выполнение, что возвращает результат тому, кто делал вызов.

В приведенном ниже примере вывод значений осуществляется с использованием генератора.

Этот код показывает наглядно использование генератора next() в приложениях. 

alist = ['Python', 'Java', 'C', 'C++', 'CSharp']




def list_items():

    for item in alist:

        yield item




gen = list_items()




iter = 0




while iter < len(alist):

    print(next(gen))

    iter += 1

Отличия этого примера от предыдущих не столь сильно выражены. Тем не менее, каждый элемент здесь возвращается генератором с использованием метода next(). Для этого сначала создается объект генератора gen, являющийся идентификатором, в котором хранится состояние генератора.

Каждый вызов next() объекта генератора приводит к выполнению вплоть до инструкции yield. После этого Python осуществляет возврат значения и сохранение его состояния для дальнейшего использования.

Применение цикла for

Помимо этого, можно использовать цикл for для осуществления итерации по объекту генератора. Тогда вызов next() не происходит явно. Тем не менее, элементы все равно последовательно возвращаются друг за другом. 

Для демонстрации цикла приводим пример генератора. 

alist = ['Python', 'Java', 'C', 'C++', 'CSharp']




def list_items():

    for item in alist:

        yield item




gen = list_items()




for item in gen:

    print(item)

Return vs yield

С помощью ключевого слова return мы задавали финальную инструкцию в функции. С ее помощью мы получили значение. Когда происходит его возврат, происходит очистка всего локального стека. И с первой инструкции уже начинается новый вызов.

Ключевое слово yield же сохраняет состояние между вызовами. Выполнение продолжается с того момента, когда управление передалось в вызывающую область. Проще говоря, непосредственно после последней инструкции yield.

Генератор или функция?

Теперь давайте сравним генератор и обычную функцию.

  1. Генератор использует yield для отправки значения пользователю, а функция имеет для этой задачи return;
  2. При использовании генератора вызовов yield может быть более одного;
  3. Вызов yield останавливает выполнение и возвращает итератор, а return всегда выполняется последним;
  4. Вызов next () выполняет функцию генератора;
  5. Локальные переменные и состояния сохраняются между последовательными вызовами метода next ();
  6. Каждый дополнительный вызов next () вызывает исключение StopIteration, если нет следующих элементов для обработки.

Давайте теперь приведем пример использования функции генератора с несколькими yield

def testGen():

    x = 2

    print('Первый yield')

    yield x




    x *= 1

    print('Второй yield')

    yield x




    x *= 1

    print('Последний yield')

    yield x




# Вызов генератора

iter = testGen()




# Вызов первого yield

next(iter)




# Вызов второго yield

next(iter)




# Вызов последнего yield

next(iter)

В каких случаях нужно использовать генератор?

Есть много ситуаций, когда полезен генератор. Вот несколько:

  • Генераторы полезны в обработке больших объемов информации. Они допускают то, что называется ленивым вычислением. Таким же способом осуществляется обработка потоков.
  • Выставление генераторов может осуществляться друг за другом, после чего их можно использовать в качестве Unix-каналов.
  • Генераторы позволяют настраивать параллельное выполнение
  • Их часто используют для чтения больших файлов. Это делает код более чистым и компактным за счет разделения процесса на более мелкие объекты.
  • Генераторы особенно полезны для парсинга веб-страниц и повышения эффективности поиска. Они позволяют перейти на страницу, выполнить определенные операции и перейти к следующей. Такой подход намного эффективнее, чем получение всех страниц сразу и использование отдельного цикла для их обработки.

Выводы

Таким образом, генераторы – это удобные для программистов инструменты, которые позволяют выполнить множество задач.

ОфисГуру
Adblock
detector