Сегодня вы узнаете для чего нужны генераторы 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 очень полезны не только для построения конвейеров данных, но и для обработки больших операций с данными, таких как чтение большого файла. Удачи!





![[Решение] Как избежать ошибки UnboundLocalError](https://egorovegor.ru/wp-content/uploads/5b77c575d7-335x220.webp)



