Выход Python, генераторы и выражения генератора

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

Видео: Генераторы Python

Генераторы в Python

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

Это долго и нелогично. В таких ситуациях на помощь приходит генератор.

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

Проще говоря, генератор - это функция, которая возвращает объект (итератор), который мы можем перебирать (по одному значению за раз).

Создать генераторы на Python

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

Если функция содержит хотя бы один yieldоператор (он может содержать другие операторы yieldили return), она становится функцией генератора. Оба yieldи returnвернут некоторое значение из функции.

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

Различия между функцией генератора и нормальной функцией

Вот чем функция генератора отличается от нормальной функции.

  • Функция генератора содержит одно или несколько yieldоператоров.
  • При вызове он возвращает объект (итератор), но не начинает выполнение немедленно.
  • Методы вроде __iter__()и __next__()реализуются автоматически. Таким образом, мы можем перебирать элементы, используя next().
  • После выполнения функции функция приостанавливается, и управление передается вызывающей стороне.
  • Локальные переменные и их состояния запоминаются между последовательными вызовами.
  • Наконец, когда функция завершается, StopIterationавтоматически вызывается при последующих вызовах.

Вот пример, иллюстрирующий все вышеизложенное. У нас есть функция генератора, названная my_gen()несколькими yieldоператорами.

 # A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n

Ниже приведен интерактивный прогон в интерпретаторе. Запустите их в оболочке Python, чтобы увидеть результат.

 >>> # It returns an object but does not start execution immediately. >>> a = my_gen() >>> # We can iterate through the items using next(). >>> next(a) This is printed first 1 >>> # Once the function yields, the function is paused and the control is transferred to the caller. >>> # Local variables and theirs states are remembered between successive calls. >>> next(a) This is printed second 2 >>> next(a) This is printed at last 3 >>> # Finally, when the function terminates, StopIteration is raised automatically on further calls. >>> next(a) Traceback (most recent call last):… StopIteration >>> next(a) Traceback (most recent call last):… StopIteration

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

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

Чтобы перезапустить процесс, нам нужно создать еще один объект-генератор, используя что-то вроде a = my_gen().

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

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

 # A simple generator function def my_gen(): n = 1 print('This is printed first') # Generator function contains yield statements yield n n += 1 print('This is printed second') yield n n += 1 print('This is printed at last') yield n # Using for loop for item in my_gen(): print(item)

Когда вы запустите программу, вывод будет:

 Это печатается первым 1 Это печатается вторым 2 Это печатается последним 3

Генераторы Python с циклом

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

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

Давайте возьмем пример генератора, который переворачивает строку.

 def rev_str(my_str): length = len(my_str) for i in range(length - 1, -1, -1): yield my_str(i) # For loop to reverse the string for char in rev_str("hello"): print(char)

Вывод

 оллех

В этом примере мы использовали range()функцию для получения индекса в обратном порядке с помощью цикла for.

Примечание . Эта функция генератора работает не только со строками, но и с другими типами итераций, такими как список, кортеж и т. Д.

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

Простые генераторы можно легко создавать "на лету" с помощью выражений генератора. Это упрощает создание генераторов.

Подобно лямбда-функциям, которые создают анонимные функции, выражения генератора создают анонимные функции генератора.

Синтаксис выражения генератора аналогичен синтаксису понимания списка в Python. Но квадратные скобки заменены круглыми скобками.

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

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

 # Initialize the list my_list = (1, 3, 6, 10) # square each term using list comprehension list_ = (x**2 for x in my_list) # same thing can be done using a generator expression # generator expressions are surrounded by parenthesis () generator = (x**2 for x in my_list) print(list_) print(generator)

Вывод

 (1, 9, 36, 100) 

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

Вот как мы можем начать получать предметы из генератора:

 # Initialize the list my_list = (1, 3, 6, 10) a = (x**2 for x in my_list) print(next(a)) print(next(a)) print(next(a)) print(next(a)) next(a)

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

 1 9 36 100 Traceback (последний вызов последним): файл "", строка 15, в StopIteration

Выражения генератора могут использоваться как аргументы функции. При таком использовании круглые скобки можно опустить.

 >>> sum(x**2 for x in my_list) 146 >>> max(x**2 for x in my_list) 100

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

Есть несколько причин, по которым генераторы являются мощной реализацией.

1. Легко реализовать

Генераторы могут быть реализованы ясным и кратким образом по сравнению с их аналогом класса итератора. Ниже приведен пример реализации последовательности степени двойки с использованием класса итератора.

 class PowTwo: def __init__(self, max=0): self.n = 0 self.max = max def __iter__(self): return self def __next__(self): if self.n> self.max: raise StopIteration result = 2 ** self.n self.n += 1 return result

Вышеупомянутая программа была длинной и запутанной. Теперь давайте сделаем то же самое, используя функцию генератора.

 def PowTwoGen(max=0): n = 0 while n < max: yield 2 ** n n += 1

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

2. Эффективная память

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

Генераторная реализация таких последовательностей удобна для памяти и предпочтительна, поскольку она производит только один элемент за раз.

3. Представьте бесконечный поток

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

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

 def all_even(): n = 0 while True: yield n n += 2

4. Конвейерные генераторы

Несколько генераторов можно использовать для конвейерной обработки серии операций. Лучше всего это проиллюстрировать на примере.

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

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

 def fibonacci_numbers(nums): x, y = 0, 1 for _ in range(nums): x, y = y, x+y yield x def square(nums): for num in nums: yield num**2 print(sum(square(fibonacci_numbers(10))))

Вывод

 4895

Эта конвейерная обработка эффективна и легко читается (и да, намного круче!).

Интересные статьи...