На этом занятии мы с вами поговорим об итераторах и
выражениях-генераторах. С генераторами списков мы уже сталкивались, например,
когда создавали список вот таким образом:
a = [x**2 for x in range(10)]
Выражения-генераторы
очень похожи на генераторы списков и в синтаксисе отличаются только круглыми
скобками:
b = (x**2 for x in range(10))
Если мы теперь
отобразим переменную b, то увидим:
<generator object <genexpr> at 0x0000020E8F429C80>
что эта
переменная ссылается на объект-генератор. Вообще,
Генератор – это
итератор, элементы которого можно перебирать (итерировать) только один раз.
И в этом
определении мы сталкиваемся с термином итератор:
Итератор – это
объект, который поддерживает функцию next() для перехода
к следующему элементу коллекции.
Наконец,
последний термин, что нам пригодится, звучит так:
Итерируемый
объект – это объект, который позволяет поочередно обойти свои элементы и может
быть преобразован к итератору.
Теперь, давайте
рассмотрим все на конкретных примерах. Самый распространенный итерируемый
объект в Python – это список:
но мы не можем
его обойти с помощью итератора, используя функцию next:
потому что
список – это не итератор. Но мы любой итерируемый объект можем легко
преобразовать в итератор с помощью функции iter:
На выходе
образуется объект-итератор для списка. Сохраним его в переменной:
Теперь, элементы
списка можно обойти с помощью этого итератора:
При первом ее
вызове она возвратит первое значение списка a и изменит
позицию итератора it, переместив его на следующий элемент. Поэтому при
втором вызове:
мы получим уже
значение второго элемента и так до конца списка:
Если вызвать
функцию next когда мы уже
дошли до конца списка, то она возвратит ошибку:
Этот пример
показывает как из итерируемого объекта можно получить итератор и с его помощью
перебрать соответствующую коллекцию и только один раз. Дойдя до конца списка,
итератор it не может
вернуться в начало и перебрать список еще раз. Этот проход по элементам
делается только один раз.
Теперь,
возвращаясь к выражению-генератору:
b = (x**2 for x in range(10))
переменную b можно
воспринимать как итератор и перебирать список через функцию next:
возвратит первое
значение. Повторный вызов:
возвратит второе
значение и так до конца списка. Здесь также мы можем выполнить перебор всех
значений только один раз, т.е. пройти список от начала до конца единожды:
вернуться и повторить операцию здесь невозможно.
Итераторы очень
удобно использовать в цикле for:
b = (x**2 for x in range(10))
for i in b:
print(i, end=" ")
Здесь нам не
нужно использовать функцию next для перехода к следующему значению. Это
автоматически выполняет оператор in в for. Но
использовать его можно только один раз. Если мы выполним цикл с этим же
итератором еще раз:
print("\nnew loop")
for i in b:
print(i, end=" ")
То в консоли
ничего не отобразится. Здесь всегда следует помнить, что итераторы перебирают
коллекцию только один раз.
Некоторые
функции, такие как:
sum, max, min
позволяют
работать непосредственно с итераторами. То есть, можно выполнять вот такие
операции:
b = (x**2 for x in range(10))
sum(b)
Будет вычислена
сумма квадратов соответствующих значений. И здесь возникает, наверное, давно
назревший вопрос: зачем вообще нужны эти выражения-генераторы? У этих объектов
есть одно существенное преимущество по сравнению с обычными списками: они не
хранят в памяти все значения сразу, а генерируют их по мере необходимости, то
есть, при проходе к следующему значению. Например, если возникает необходимость
оперировать очень большим списком:
lst = list(range(1000000000000))
то у компьютера
попросту не хватит памяти и возникнет ошибка. Но генераторы-выражения смогут
спокойно перебирать значения такой коллекции:
lst = (x for x in range(1000000000))
например, в цикле for:
lst = (x for x in range(1000000000))
for i in lst:
print(i, end=" ")
if i > 100: break
И работать все
это будет достаточно быстро, так как lst не хранит в памяти элементы, а
вычисляет их налету в цикле for. Правда, из-за этого нельзя определить
число элементов в генераторе при помощи функции len:
a = (x for x in range(10, 20))
len(a)
или получить
доступ к его отдельному элементу по индексу:
Если нужно
выполнить эти операции, то любое выражение-генератор можно превратить в обычный
список. Для этого используется функция list:
a = (x for x in range(10, 20))
b = list(a)
В результате,
переменная b ссылается на
список:
[10, 11, 12, 13,
14, 15, 16, 17, 18, 19]
с которым мы уже
можем работать как со списком.