Генераторы Python — класс функций, которые позволяют создавать и хранить в памяти собственные итераторы, возвращающие значение функций по запросу. Генераторы обрабатывают данные порционно, шаг за шагом, и применяются при работе с большими объемами информации, когда возникает риск переполнения памяти. Необходимость использования генераторов может возникнуть в следующих случаях:
- работа с большими файлами (десятки гигабайт): сравнение строк нескольких таких файлов, обработка, поиск по условию и т. д.;
- веб-скрейпинг;
- анализ бесконечного потока данных (сетевой трафик, биржевые котировки, показания приборов, датчиков);
- математические вычисления;
- рефакторинг (переработка) существующего кода.
Не стоит путать генераторы как объекты (функции) со следующими конструкциями:
- генераторы списков;
- генераторы множеств.
Списки и множества не имеют отношения к генераторам-объектам. В данном случае слово «генератор» выступает лишь синтаксической конструкцией, возникающей при переводе с английского «list comprehension».
Рассмотрим сначала генераторы списков и множеств, чтобы в дальнейшем не возникало путаницы при использовании непосредственно самих генераторов.
Генераторы списков
Для того чтобы создать длинный список программным методом, используется цикл с дополнительным условием. Выражение заключается в квадратные скобки и в общем виде будет выглядеть как:
[выражение for item in итерируемое множество if условие].
Создадим сначала список чисел от 1 до 30, а затем из этого диапазона выберем только четные числа:
nums = [i for i in range(1, 10)]
even_nums = [i for i in range(1, 10) if n % 2 == 0]
print(nums)
print(even_nums)
>
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 4, 6, 8]
В примере выражение является самим элементом item, числа от 1 до 30 — итерируемое множество, делимость нацело без остатка — условие.
Генераторы списков Python относят к «синтаксическому сахару» и используют для сокращения количества кода.
Генераторы множеств
Представляют собой выражение вида:
{выражение for item in итерируемое множество if условие}.
Как видим, от списков отличается только наличием фигурных скобок. Генераторы множеств можно использовать для того, чтобы отфильтровать ненужные элементы в каком-то множестве. Например, нам нужно отобрать файлы с расширением txt:
txt = {t for t in files if t.lower().endswith((«.txt))}
Генераторы множеств позволяют использовать несколько циклов по разным итерируемым множествам с условными выражениями, создавать множества множеств или сделать множество фиксированным.
Выражения-генераторы
Объявление генераторных выражений синтаксически похоже на создание словарей или списков, но, в отличие от списков, генераторы не хранят все значения выполняемой функции в памяти, а только последний элемент последовательности, условие перехода или прерывания.
Возьмем в качестве примера генераторное выражение, возводящее в куб числа от 1 до 5:
n = (i**3 for i in range(1, 6))
print(n)
>
<generator object <genexpr> at 0x7fb6dd886510>
При выводе переменной n на печать интерпретатор сообщает о том, что созданный объект — генератор. Для вычисления очередного значения используется метод next():
print(next(n))
>1
print(next(n))
>8
print(next(n))
>27
print(next(n))
>64
print(next(n))
>125
print(next(n))
>
StopIteration
Выполним то же самое с использованием цикла for:
n = (i**3 for i in range(1, 6))
for i in n:
print(i)
>
1
8
27
64
125
Метод next(n) последовательно рассчитывает и выводит значения:
1, 8, 27, 64, 125. При этом происходит стирание предыдущих значений из памяти. Во время 6-й итерации все значения будут удалены, поэтому интерпретатор вызовет исключение StopIteration.
Таким образом, генератор позволяет лишь единожды пройтись по элементам объекта и его повторное использование невозможно — нужно создавать новый.
Функции-генераторы
В предыдущем разделе мы рассмотрели генератор вида:
(выражение for item in итерируемый объект if условие).
Это выражение — частный случай использования функции-генератора. Генераторная функция определяется при помощи той же инструкции, что и обычная, — def. Отличие заключается в использовании yield, возвращающей объект-генератор, вместо инструкции return, сразу же возвращающей значение. Рассмотрим работу функции-генератора на примере чисел Фибоначчи:
def func(n):
fb1 = 0
fb2 = 1
for i in range(1,n):
yield fb2
fb_sum = fb1+fb2
fb1 = fb2
fb2 = fb_sum
fb = func(7)
print(fb)
for i in fb:
print(i)
>
<generator object func at 0x7f87e275e510>
1
1
2
3
5
8
Как видим, из консоли при вызове функции func(7) создается генератор fb.
При каждой новой итерации оператор yield приостанавливает выполнение следующих инструкций и возвращает значение fb2. Дальнейшие вызовы метода next(fb) приведут к тому, что выполнение функции продолжится с начала до следующего оператора yield. При этом, как можем заметить, значения промежуточных переменных fb1 и fb_sum фиксируются и изменяются в соответствии с кодом.
Функция-генератор поддерживает использование нескольких операторов yield. В этом случае возвратится значение справа от оператора, а код при последующем next() выполнится так же до следующего yield. Таким образом можно создавать сопрограммы-функции с возможностью приостанавливать и возобновлять выполнение программы в точках входа и выхода yield.
Рассмотрим еще один пример, где функция-генератор воспроизводит бесконечно длинную последовательность чисел.
def halfs(next_half = 0.0):
while True:
yield next_half
next_half += 0.5
Функция возвращает числа 0, 0.5, 1, 1.5 и т. д. до бесконечности.
Воспользуемся этой функцией для создания списка чисел, удовлетворяющих условию n < 3:
res = []
for i in halfs():
res. append(i)
if i >= 2.0:
break
print (res)
>
[0.0, 0.5, 1.0, 1.5, 2.0]
При каждом вызове функция halfs возвращает генератор, который увеличивает текущее значение на 0.5.
Значение можно не только вернуть, но еще и передать его в генератор с помощью метода send()
def triple_numbers():
while True:
n = yield
yield n * 3
g = triple_number()
next(g)
print(g.send(10))
next(g)
print(g.send(50))
>
10
50
При помощи метода throw() можно обрабатывать исключения:
def triple_numbers(num):
while True:
num *= 3
yield num
gen = triple_numbers(3)
for i in gen:
if i > 100:
gen.throw(Exception(«Error!»))
print(i)
>
9
27
81
Exception: Error!
Программа создает генератор, бесконечно умножающий пользовательское число на 3 до тех пор, пока оно не превысит 100, что вызовет исключение Error.
При помощи метода close() можно принудительно остановить выполнение функции генератора:
def triple_numbers(num):
while True:
num *= 3
yield num
gen = triple_numbers(3)
for i in gen:
if i > 100:
gen.close()
print(i)
>
9
27
81
243
Вызов метода close() генерирует исключение GeneratorExit в точке последнего вызова yield. Нет необходимости перехватывать это исключение, поскольку close() делает это автоматически. При этом сообщения об ошибке не возникает и последняя инструкция print(i) в цикле for успешно выполняется (на печать выводится число 243).
Изучить применение генераторов на реальных практических задачах можно на курсах DevEducation. Программисты Python часто выбирают этот язык программирования в качестве своего первого, поскольку он отличается минималистичным и интуитивно понятным синтаксисом, простотой, многофункциональностью и универсальностью, востребованностью на рынке.
Освоение Python с нуля до уровня джуна при должном желании может занять не больше года. Курсы помогут существенно ускорить этот процесс и дать практический опыт в решении конкретных реальных задач, который невозможно найти на страницах справочников и самоучителей.