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

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

Сегодня вы узнаете для чего нужны генераторы 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: &gt; 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)])")

Вывод программы:

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

Второй вариант:

import cProfile

cProfile.run("sum((i for i in range(1, 25000000)))")

Вывод программы:

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

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

Конвейер данных

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

Егор Егоров

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

Ссылка на мой github есть в шапке. Залетай.

Оцените автора
Егоров Егор
Добавить комментарий