Курс по 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():
пока не получим
исключение StopIteration. Соответственно, пройтись по значениям генератора можно
только один раз. Повторно их перебрать уже не получится.
Помимо функции next() генераторы
можно перебирать и в цикле for, так как они являются итерируемыми
объектами:
gen = (x ** 2 for x in range(6))
for x in gen:
print(x)
Но, опять же, перебрать
его можно только один раз, поэтому повторный цикл for не выведет
никаких значений:
Некоторые
функции, такие как:
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)
или получить
доступ к его отдельному элементу по индексу:
так как этих
элементов попросту нет, они генерируются последовательно при вызове функции next().
Если все же
требуется работать со значениями генератора, как с элементами списка, то его
сначала нужно преобразовать в этот список, а затем со списком и работать:
a = (x for x in range(10, 20))
b = list(a)
И, опять же,
если попробовать преобразовать этот же генератор к списку еще раз:
то получим
пустой список, так как элементы генератора уже были перебраны в первой функции list() и сделать это
повторно не получится.
Также не
получится преобразовать генератор к списку, используя квадратные скобки:
[(x ** 2 for x in range(6))]
В этом случае
первым элементом списка будет ссылка на объект-генератор, не более того. Также
это означает, что у генераторов списков нельзя внутри прописывать круглые
скобки, как это сделано сейчас. Теперь мы знаем, что это определение
выражения-генератора внутри обычного списка.
Итак, из этого
занятия вы должны были узнать, что из себя представляют выражения-генераторы,
как перебираются их элементы и для чего они могут понадобиться. Помнить об
ограничениях, накладываемые на генераторы, связанные с генерацией значений «на
лету», то есть, нельзя использовать функцию len(), обращаться
по индексу и перебирать его элементы только один раз. Для закрепления этого
материала пройдите практические задания и переходите к следующему уроку.
Курс по Python: https://stepik.org/course/100707