Генераторы Python

Что такое генератор? Генератор Python [https://wiki.python.org/moin/Generators] - это функция, которая производит последовательность результатов. Он работает, сохраняя свое локальное состояние, так что функция может возобновиться снова с того места, где она была остановлена, при последующем вызове. Таким образом, вы можете думать о генераторе как о мощном итераторе. Состояние функции поддерживается с помощью ключевого слова yield, имеющего следующий синтаксис: yield [список_выражений] Это ключевое слово Python

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

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

Состояние функции поддерживается с помощью ключевого слова yield , имеющего следующий синтаксис:

 yield [expression_list] 

Это ключевое слово Python работает так же, как return , но имеет некоторые важные отличия, которые мы объясним в этой статье.

Генераторы были введены в PEP 255 вместе с оператором yield Они доступны, начиная с версии Python 2.2.

Как работают генераторы Python?

Чтобы понять, как работают генераторы, давайте воспользуемся простым примером ниже:

 # generator_example_1.py 
 
 def numberGenerator(n): 
 number = 0 
 while number < n: 
 yield number 
 number += 1 
 
 myGenerator = numberGenerator(3) 
 
 print(next(myGenerator)) 
 print(next(myGenerator)) 
 print(next(myGenerator)) 

В приведенном выше коде определяется генератор с именем numberGenerator , который получает значение n в качестве аргумента, а затем определяет и использует его в качестве предельного значения в цикле while. Кроме того, он определяет переменную с именем number и присваивает ей нулевое значение.

myGenerator » генератора (myGenerator) с помощью next() запускает код генератора до первого yield , который в данном случае возвращает 1.

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

Вызов функции еще два раза дает нам следующие 2 числа в последовательности, как показано ниже:

 $ python generator_example_1.py 
 0 
 1 
 2 

Если бы мы снова вызвали этот генератор, мы получили бы StopIteration поскольку он завершился и вернулся из своего внутреннего цикла while.

Эта функциональность полезна, потому что мы можем использовать генераторы для динамического создания итераций на лету. Если бы мы myGenerator list() , мы бы получили массив чисел (например, [0, 1, 2] ) вместо объекта-генератора, с которым немного легче работать в некоторых приложениях.

Разница между доходностью и доходностью

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

С другой стороны, yield поддерживает состояние между вызовами функций и возобновляет работу с того места, где было остановлено, когда мы снова next() Итак, если yield вызывается в генераторе, то при следующем вызове того же генератора мы возьмем обратно сразу после последнего оператора yield

Использование возврата в генераторе

Генератор может использовать оператор return , но только без возвращаемого значения, то есть в форме:

 return 

Когда генератор находит return , он действует так же, как и при возврате любой другой функции.

Как говорится в PEP 255:

Обратите внимание, что return означает «Я закончил, и мне нечего возвращать» как для функций генератора, так и для функций, не являющихся генераторами.

Давайте изменим наш предыдущий пример, добавив предложение if-else, которое будет различать числа выше 20. Код выглядит следующим образом:

 # generator_example_2.py 
 
 def numberGenerator(n): 
 if n < 20: 
 number = 0 
 while number < n: 
 yield number 
 number += 1 
 else: 
 return 
 
 print(list(numberGenerator(30))) 

В этом примере, поскольку наш генератор не выдаст никаких значений, это будет пустой массив, так как число 30 больше 20. Таким образом, оператор return работает аналогично оператору break в этом случае.

Это можно увидеть ниже:

 $ python generator_example_2.py 
 [] 

Если бы мы присвоили значение меньше 20, результаты были бы аналогичны первому примеру.

Использование next () для перебора генератора

Мы можем проанализировать значения, выданные генератором, используя метод next() , как показано в первом примере. Этот метод указывает генератору возвращать только следующее значение итерации , но ничего больше.

Например, следующий код напечатает на экране значения от 0 до 9.

 # generator_example_3.py 
 
 def numberGenerator(n): 
 number = 0 
 while number < n: 
 yield number 
 number += 1 
 
 g = numberGenerator(10) 
 
 counter = 0 
 
 while counter < 10: 
 print(next(g)) 
 counter += 1 

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

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

Выполнение приведенного выше кода приведет к следующему результату:

 $ python generator_example_3.py 
 0 
 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 

Примечание . Однако существует разница в синтаксисе между Python 2 и 3. В приведенном выше коде используется версия Python 3. В Python 2 функция next() может использовать предыдущий или следующий синтаксис:

 print(g.next()) 

Что такое генераторное выражение?

Выражения генератора похожи на составные части списка , но они возвращают генератор вместо списка. Они были предложены в PEP 289 и стали частью Python с версии 2.4.

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

Например, наш предыдущий код можно было изменить с помощью выражений генератора следующим образом:

 # generator_example_4.py 
 
 g = (x for x in range(10)) 
 print(list(g)) 

Результаты будут такими же, как и в наших первых нескольких примерах:

 $ python generator_example_4.py 
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 

Выражения генератора полезны при использовании функций сокращения, таких как sum() , min() или max() , поскольку они сокращают код до одной строки. Они также намного короче, чем полная функция генератора Python. Например, следующий код суммирует первые 10 чисел:

 # generator_example_5.py 
 
 g = (x for x in range(10)) 
 print(sum(g)) 

После запуска этого кода результат будет:

 $ python generator_example_5.py 
 45 

Управление исключениями

Одна важная вещь , чтобы отметить , что yield ключевое слово не допускается в try части Try / наконец конструкции. Таким образом, производителям следует с осторожностью распределять ресурсы.

Тем не менее, yield может появиться в finally положении, за except положений, или в try части попытки / за исключением положений.

Например, мы могли бы создать следующий код:

 # generator_example_6.py 
 
 def numberGenerator(n): 
 try: 
 number = 0 
 while number < n: 
 yield number 
 number += 1 
 finally: 
 yield n 
 
 print(list(numberGenerator(10))) 

В приведенном выше коде в результате предложения finally число 10 включается в вывод, а результат представляет собой список чисел от 0 до 10. Обычно этого не происходит, поскольку условный оператор number < n . Это можно увидеть в выводе ниже:

 $ python generator_example_6.py 
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 

Отправка значений генераторам

У генераторов есть мощный инструмент в виде send() для генераторов-итераторов. Этот метод был определен в PEP 342 и доступен, начиная с версии Python 2.5.

Метод send() возобновляет работу генератора и отправляет значение, которое будет использоваться для продолжения следующего yield . Метод возвращает новое значение, полученное генератором.

Синтаксис: send() или send(value) . Без какого-либо значения метод send эквивалентен вызову next() Этот метод также может использовать None в качестве значения. В обоих случаях результатом будет то, что генератор перейдет к первому выражению yield

Если генератор завершает работу без получения нового значения (например, при использовании return ), метод send() вызывает StopIteration .

В следующем примере показано использование send() . В первой и третьей строках нашего генератора мы просим программу присвоить number переменной значение, полученное ранее. В первой строке после нашей функции генератора мы создаем экземпляр генератора и генерируем первый yield в следующей строке, вызывая next функцию. Таким образом, в последней строке мы отправляем значение 5, которое будет использоваться генератором в качестве входных данных и рассматриваться как его предыдущий выход.

 # generator_example_7.py 
 
 def numberGenerator(n): 
 number = yield 
 while number < n: 
 number = yield number 
 number += 1 
 
 g = numberGenerator(10) # Create our generator 
 next(g) # 
 print(g.send(5)) 

Примечание . Поскольку при первом создании генератора нет возвращаемого значения, перед использованием send() мы должны убедиться, что генератор выдал значение с помощью next() или send(None) . В приведенном выше примере мы выполняем next(g) именно по этой причине, иначе мы получим сообщение об ошибке «TypeError: невозможно отправить значение, отличное от None, только что запущенному генератору».

После запуска программы он выводит на экран значение 5, которое мы ему отправили:

 $ python generator_example_7.py 
 5 

Третья строка нашего генератора сверху также показывает новую функцию Python, представленную в том же PEP: выражения yield. Эта функция позволяет использовать yield в правой части оператора присваивания. Значение выражения yield - None , пока программа не вызовет метод send(value) .

Подключение генераторов

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

Новое выражение определено в PEP 380, и его синтаксис:

 yield from <expression> 

где <expression> - это выражение, оцениваемое как итеративное, которое определяет делегирующий генератор.

Давайте посмотрим на это на примере:

 # generator_example_8.py 
 
 def myGenerator1(n): 
 for i in range(n): 
 yield i 
 
 def myGenerator2(n, m): 
 for j in range(n, m): 
 yield j 
 
 def myGenerator3(n, m): 
 yield from myGenerator1(n) 
 yield from myGenerator2(n, m) 
 yield from myGenerator2(m, m+5) 
 
 print(list(myGenerator1(5))) 
 print(list(myGenerator2(5, 10))) 
 print(list(myGenerator3(0, 10))) 

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

Последние три строки кода выводят на экран три списка, сгенерированных каждым из трех ранее определенных генераторов. Как мы видим, когда мы запускаем программу ниже, в результате myGenerator3 использует доходность, полученную от myGenerator1 и myGenerator2 , для создания списка, который объединяет предыдущие три списка.

В примере также показано важное применение генераторов: возможность разделить длинную задачу на несколько отдельных частей, что может быть полезно при работе с большими наборами данных.

 $ python generator_example_8.py 
 [0, 1, 2, 3, 4] 
 [5, 6, 7, 8, 9] 
 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] 

Как видите, благодаря yield from генераторы можно объединять в цепочку для более динамичного программирования.

Преимущества генераторов

  1. Упрощенный код

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

  1. Лучшая производительность

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

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

Когда использовать генераторы

Генераторы - это продвинутый инструмент, присутствующий в Python. Есть несколько случаев программирования, в которых генераторы могут повысить эффективность. Вот некоторые из этих случаев:

  • Обработка больших объемов данных: генераторы обеспечивают вычисление по запросу, также называемое ленивым вычислением. Этот метод используется при потоковой обработке.
  • Трубопровод: штабелированные генераторы могут использоваться как каналы, аналогично каналам Unix.
  • Параллелизм: генераторы могут использоваться для генерации (имитации) параллелизма.

Заключение

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

Эти аспекты еще более очевидны в выражениях генератора, где одна строка кода может резюмировать последовательность операторов.

Работоспособность генераторов была улучшена за send() , и расширенных операторов, таких как yield from .

Благодаря этим свойствам у генераторов есть много полезных приложений, таких как создание каналов, параллельное программирование и помощь в создании потоков из больших объемов данных.

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

Для чего вы использовали генераторы? Дайте нам знать об этом в комментариях!

comments powered by Disqus