В этом руководстве вы узнаете, как легко создавать итерации с помощью генераторов 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
Эта конвейерная обработка эффективна и легко читается (и да, намного круче!).