Сегодня вы узнаете для чего нужны генераторы Python и как их применять в программировании.
Введение
Генератор — это тип коллекции, в котором элементы создаются во время выполнения. Использование генераторов улучшает производительность приложений.
Генераторы Python очень полезны для обработки операций, требующих большого объема памяти.
Использование
Давайте сразу начнем с простого примера. Ниже функция выводит бесконечную последовательность чисел:
def func_gen(): i = 0 while True: yield i i += 1 f = func_gen() next(f)
Вывод программы:
'0'
Продолжаем:
next(f)
Вывод программы:
1
Ещё раз:
next(f)
Вывод программы:
2
И так далее.. Я думаю, алгоритм понятен.
Оператор yield
Хорошо, давайте вернемся к нашей функции func_gen. Что происходит в приведенном ниже коде?
Внутри цикла while у нас есть оператор yield. Yield выходит из цикла и возвращает управление тому, кто вызвал функцию func_gen. В строке f=func_gen, f теперь является генератором, как показано ниже:
def func_gen(): i = 0 while True: yield i i += 1 f = func_gen() f
Вывод программы:
<generator object func_gen at 0x7fd4a781fac0>
Как только у вас есть функция генератора, вы можете перебирать ее с помощью функции next. Поскольку у меня есть бесконечный цикл while в функции func_gen, я могу вызывать итератор столько раз, сколько захочу. Каждый раз, когда я использую next, генератор запускает выполнение с предыдущей позиции и печатает новое значение.
Использование генератора в выражениях
Генераторы Python можно использовать вне функции без yield.
Посмотрите на приведенный ниже пример:
f = (i for i in range(50)) f
Вывод программы:
<generator object at 0x1095085f0>
(i for i in range(50)) — это объект генератора Python. Синтаксис очень похож на генератор списка Python, за исключением того, что вместо квадратных скобок генераторы определяются с помощью круглых скобок. Как обычно, как только у нас есть объект generator, мы можем вызвать на нем итератор nex, чтобы вывести значения, как показано ниже:
next(f)
Вывод программы:
'0'
И ещё раз:
next(f)
Вывод программы:
1
Подробнее почитать про генераторы списков можно в этой статье.
Использование исключения StopIteration
Генераторы Python выдадут исключение StopIteration, если для итератора нет возвращаемого значения.
Давайте рассмотрим следующий пример:
def test_range(): for i in range(0, 1): yield i i = test_range() next(i)
Вывод программы:
'0'
Продолжаем:
next(i)
И видим следующее:
Traceback (most recent call last): File "/Users/krnlnx/Projects/Test/app.py", line 9, in next(i) StopIteration
Итак, чтобы избежать вышеуказанной ошибки, мы можем «поймать» такое исключение и остановить итерацию:
def test_range(): for i in range(0, 1): yield i i = test_range() try: print(next(i)) except StopIteration: print("Iteration end")
Вывод программы:
'0'
Повторим этот блок еще раз:
try: print(next(i)) except StopIteration: print("Iteration end")
Получаем вывод:
Iteration end
Подробнее про исключения можно почитать в этой статье
Функция генератора send
Мы можем передать значение генераторам Python с помощью функции send():
def new_func(): while True: i = yield yield i + 1 f = new_func() next(f) print(f.send(7))
Вывод программы:
8
Рекурсивный генератор
Генераторы Python можно использовать рекурсивно. Проверьте приведенный ниже код. В приведенной ниже функции yield from generator_factorial(n — 1) — это рекурсивный вызов функции generator_factorial():
def fact_gen(num): if num == 1: fact = 1 else: i = yield from fact_gen(num - 1) fact = num * i yield fact return fact f = fact_gen(3) next(f)
Вывод программы:
1
Продолжаем:
next(f)
Вывод программы:
2
Ещё раз:
next(f)
Вывод программы:
6
Метод throw
Продолжая приведенный выше пример, предположу, что я хочу, чтобы генератор выдавал ошибку для факториала числа больше 100. Я могу добавить исключение «generator.throw()», как показано ниже:
num = 100 if num >= 100: f.throw(ValueError, " > 100 ")
Получаем вывод программы:
Traceback (most recent call last): File "/Users/krnlnx/Projects/Test/app.py", line 17, in g.throw(ValueError, "Only numbers less than 100 are allowed") File "/Users/krnlnx/Projects/Test/app.py", line 5, in generator_factorial a = yield from generator_factorial(n - 1) File "/Users/krnlnx/Projects/Test/app.py", line 5, in generator_factorial a = yield from generator_factorial(n - 1) File "/Users/krnlnx/Projects/Test/app.py", line 7, in generator_factorial yield f ValueError: > 100
Использование памяти генератором
Генераторы Python занимают очень мало памяти. Давайте рассмотрим следующие два примера. В приведенных ниже строк код обратите внимание на разницу между байтовым размером памяти, используемым списком и генератором:
import sys seq = [i for i in range(1, 3000000)] sys.getsizeof(seq)
Вывод программы:
24387832
Идем дальше:
import sys seq = (i for i in range(1, 3000000)) sys.getsizeof(seq)
Вывод программы:
112
Тестирование производительности генератора
Здесь следует отметить одну вещь: генераторы в Python работают медленнее, чем генераторы списков, если память достаточно велика для вычислений. Давайте рассмотрим ниже два примера с точки зрения производительности:
import cProfile cProfile.run("sum([i for i in range(1, 25000000)])")
Вывод программы:
Второй вариант:
import cProfile cProfile.run("sum((i for i in range(1, 25000000)))")
Вывод программы:
Проверьте количество вызовов функций и время, которое потребовалось генератору для вычисления суммы по сравнению с генератором списков.
Конвейер данных
Пожалуй, я закончу эту статью с конвейерами данных. Генераторы Python отлично подходят для построения конвейеров.
Давайте откроем CSV-файл и переберем его с помощью генератора:
def read_csv_table(): for i in open('table.csv'): yield i f = read_csv_table() next(f)
Вывод программы:
'Date,Open,High,Low,Close,Adj Close,Volume'
Далее:
next(f)
Вывод программы:
'1996-08-09,14.250000,16.750000,14.250000,16.500000,15.324463,1601500'
Допустим, я хочу заменить запятые в CSV для каждой строки пробелами, для этого я могу построить конвейер:
f1 = (i for i in open('table.csv')) f2 = (a.replace(",", " ") for a in f1) next(f2)
Вывод программы:
'Date Open High Low Close Adj Close Volume'
Продолжаем:
next(f2)
Получаем вывод программы:
'1996-08-09 14.250000 16.750000 14.250000 16.500000 15.324463 1601500'
И наконец пишем:
next(f2)
Итоговый вывод программы:
'1996-08-12 16.500000 16.750000 16.375000 16.500000 15.324463 260900'
Заключение
Вам нужно немного практики, чтобы овладеть генераторами Python. Но я хочу сказать, что после освоения генераторы Python очень полезны не только для построения конвейеров данных, но и для обработки больших операций с данными, таких как чтение большого файла. Удачи!