Выражения генераторы

Курс по Python: https://stepik.org/course/100707

На этом занятии мы с вами поговорим о выражениях-генераторах. Что это такое? Смотрите, когда мы рассматривали генераторы списков, то в квадратных скобках описывали некий алгоритм формирования значений списка:

a = [x for x in range(1, 5)]

Так вот, все эти значения были сгенерированы внутренним генератором, с помощью которого и заполнялся весь этот список.

То же самое и для генерации множеств и словарей. Здесь также внутренний генератор автоматически заполнял значениями эти коллекции. Так вот, в Python можно определить такой генератор без привязки к какой-либо коллекции. Для этого используется синтаксис такой же, как и для генераторов коллекций, только все записывается в круглых скобках:

(<формирование значения> for <переменная> in <итерируемый объект>)

Обратите внимание, круглые скобки здесь не означают кортеж. Генераторов кортежей не существует. Когда мы пишем круглые скобки, то получаем на выходе «чистый» генератор. Например, строчка:

a = (x ** 2 for x in range(6))

задает генератор для формирования квадратов целых чисел от 0 до 5. Если выведем значение переменной a, то увидим, что это объект-генератор:

<generator object <genexpr> at 0x032DEAE0>

Но как получить нам конкретные значения из этого генератора? Очень просто! Генератор сам по себе является также и итератором, то есть, его значения можно перебирать с помощью функции next():

next(a)
…
next(a)

пока не получим исключение StopIteration. Соответственно, пройтись по значениям генератора можно только один раз. Повторно их перебрать уже не получится.

Помимо функции next() генераторы можно перебирать и в цикле for, так как они являются итерируемыми объектами:

gen = (x ** 2 for x in range(6))
 
for x in gen:
    print(x)

Но, опять же, перебрать его можно только один раз, поэтому повторный цикл for не выведет никаких значений:

for x in gen:
    print(x)

Некоторые функции, такие как:

list, set, sum, max, min и другие

позволяют работать непосредственно с итераторами. То есть, в качестве аргумента им можно передавать генератор, например:

a = (x ** 2 for x in range(6))
list(a)

будет сформирован список из значений на выходе генератора. Или так:

a = (x ** 2 for x in range(6))
set(a)

получим множество из значений генератора. То же самое и для других функций:

sum((x ** 2 for x in range(6)))
max((x ** 2 for x in range(6)))

И еще раз обратите внимание, использовать эти и другие подобные им функции для одного и того же генератора можно только один раз. Например, вызывая функцию sum() дважды:

a = (x ** 2 for x in range(6))
sum(a)
sum(a)

Во втором случае увидим 0, так как элементы генератора нельзя обойти второй раз.

Но здесь остается один важный вопрос: зачем вообще нужны эти выражения-генераторы? Дело в том, что эти объекты по сравнению, например, с обычными списками не хранят в памяти все значения сразу, а генерируют их по мере необходимости, то есть, при переходе к следующему значению. Например, если возникает необходимость оперировать очень большим списком:

lst = list(range(1000000000000))

то у компьютера попросту не хватит памяти для его хранения и возникнет ошибка. А вот с генераторами таких проблем не возникнет, так как они не хранят все числа сразу, а формируют их по мере необходимости «на лету»:

lst = (x for x in range(1000000000))
 
for i in lst:
    print(i, end=" ")
    if i > 50:
        break

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

a = (x for x in range(10, 20))
len(a)

или получить доступ к его отдельному элементу по индексу:

a[2]

так как этих элементов попросту нет, они генерируются последовательно при вызове функции next().

Если все же требуется работать со значениями генератора, как с элементами списка, то его сначала нужно преобразовать в этот список, а затем со списком и работать:

a = (x for x in range(10, 20))
b = list(a)

И, опять же, если попробовать преобразовать этот же генератор к списку еще раз:

b2 = list(a)

то получим пустой список, так как элементы генератора уже были перебраны в первой функции list() и сделать это повторно не получится.

Также не получится преобразовать генератор к списку, используя квадратные скобки:

[(x ** 2 for x in range(6))]

В этом случае первым элементом списка будет ссылка на объект-генератор, не более того. Также это означает, что у генераторов списков нельзя внутри прописывать круглые скобки, как это сделано сейчас. Теперь мы знаем, что это определение выражения-генератора внутри обычного списка.

Итак, из этого занятия вы должны были узнать, что из себя представляют выражения-генераторы, как перебираются их элементы и для чего они могут понадобиться. Помнить об ограничениях, накладываемые на генераторы, связанные с генерацией значений «на лету», то есть, нельзя использовать функцию len(), обращаться по индексу и перебирать его элементы только один раз. Для закрепления этого материала пройдите практические задания и переходите к следующему уроку.

Курс по Python: https://stepik.org/course/100707

Видео по теме