Генераторы
В Python просто генераторы и генераторы списков - разные вещи. Здесь есть проблема перевода с английского. То, что мы привыкли называть генератором списка, в английском варианте звучит как "list comprehension" и к генераторам никакого отношения не имеет.
Слово "comprehension" (понимание, осмысление) оказывается как бы не в тему при переводе на русский. Получается что-то вроде "понимание списка". Поэтому мы говорим "генератор списка", понимая под словом "генератор" не объект, а синтаксическую конструкцию, которая генерирует, то есть создает, список.
С другой стороны, объекты-генераторы - это особые объекты-функции, которые между вызовами сохраняют свое состояние. В цикле for они ведут себя подобно итерируемым объектам, к которым относятся списки, словари, строки и др. Однако генераторы поддерживают метод __next__()
, а значит являются разновидностью итераторов.
Быстрым способом создания относительно простых объектов-генераторов являются генераторные выражения - generator expressions. Синтаксис этих выражений похож на синтаксис генераторов списков. Однако они возвращают разные типы объектов. Первый - объект-генератор. Второй - список.
Сначала рассмотрим генераторы списков, чтобы привыкнуть к синтаксической конструкции.
Генераторы списков
В Python генераторы списков позволяют создавать и быстро заполнять списки.
Синтаксическая конструкция генератора списка предполагает наличие итерируемого объекта или итератора, на базе которого будет создаваться новый список, а также выражение, которое будет что-то делать с извлеченными из последовательности элементами перед тем как добавить их в формируемый список.
>>> a = [1, 2, 3]
>>> b = [i+10 for i in a]
>>> a
[1, 2, 3]
>>> b
[11, 12, 13]
В примере выше генератором списка является выражение [i+10 for i in a]
. Здесь a - итерируемый объект. В данном случае это другой список. Из него извлекается каждый элемент в цикле for
. Перед for
описывается действие, которое выполняется над элементом перед его добавлением в новый список.
Обратите внимание, что генератор создает новый список, а не изменяет существующий. Если надо изменить текущую переменную, ей надо присвоить новое значение:
>>> a = [1, 2, 3]
>>> a = [i+10 for i in a]
>>> a
[11, 12, 13]
Генераторы списков относятся к разряду "синтаксического сахара" языка программирования Python. Другими словами, без них можно обойтись:
>>> for index, value in enumerate(a):
... a[index] = value + 10
...
>>> a
[11, 12, 13]
Если в программе может быть несколько ссылок на список, генераторами надо пользоваться осторожно:
>>> ls0 = [1,2,3]
>>> ls1 = ls0
>>> ls1.append(4)
>>> ls0
[1, 2, 3, 4]
>>> ls1 = [i+1 for i in ls1]
>>> ls1
[2, 3, 4, 5]
>>> ls0
[1, 2, 3, 4]
Здесь мы предполагаем, что изменение списка через одну переменную, будут видны через другую. Однако если изменить список генератором, то переменные будут указывать на разные списки.
Перебираемым в цикле for
объектом может быть быть не только список. В примере ниже в список помещаются строки файла.
>>> lines = [line.strip() for line in open('text.txt')]
>>> lines
['one', 'two', 'three']
В генератор списка можно добавить условие:
>>> from random import randint
>>> nums = [randint(10, 20) for i in range(10)]
>>> nums
[18, 17, 11, 11, 15, 18, 11, 20, 10, 19]
>>> nums = [i for i in nums if i%2 == 0]
>>> nums
[18, 18, 20, 10]
Генераторы списков могут содержать вложенные циклы:
>>> a = "12"
>>> b = "3"
>>> c = "456"
>>> comb = [i+j+k for i in a for j in b for k in c]
>>> comb
['134', '135', '136', '234', '235', '236']
Генераторы словарей и множеств
Если в выражении генератора списка заменить квадратные скобки на фигурные, то можно получить не список, а словарь:
>>> a = {i:i**2 for i in range(11,15)}
>>> a
{11: 121, 12: 144, 13: 169, 14: 196}
При этом синтаксис выражения до for
должен быть соответствующий словарю, то есть включать ключ и через двоеточие значение. Если этого нет, будет сгенерировано множество:
>>> a = {i for i in range(11,15)}
>>> a
set([11, 12, 13, 14])
>>> b = {1, 2, 3}
>>> b
set([1, 2, 3])
Генераторы
Выражения, создающие объекты-генераторы, похожи на выражения, генерирующие списки, словари и множества за одним исключением. Чтобы создать генераторный объект, надо использовать круглые скобки:
>>> a = (i for i in range(2, 8))
>>> a
<generator object <genexpr> at 0x7efc88787910>
>>> for i in a:
... print(i)
...
2
3
4
5
6
7
Второй раз перебрать генератор в цикле for
не получится, так как объект-генератор уже сгенерировал все значения по заложенной в него "формуле". Поэтому генераторы обычно используются, когда надо единожды пройтись по итерируемому объекту.
Кроме того, генераторы экономят память, так как в ней хранятся не все значения, скажем, большого списка, а только предыдущий элемент, предел и формула, по которой вычисляется следующий элемент.
Выражение, создающее генератор, это сокращенная запись следующего:
>>> def func(start, finish):
... while start < finish:
... yield start * 0.33
... start += 1
...
>>> a = func(1, 4)
>>> a
<generator object func at 0x7efc88787a50>
>>> for i in a:
... print(i)
...
0.33
0.66
0.99
Функция, содержащая yield
возвращает объект-генератор, а не выполняет свой код сразу. Тело функции исполняется при каждом вызове метода __next__()
. В цикле for
это делается автоматически. При этом функция сохраняет значения переменных от предыдущего вызова.
Если нет необходимости использовать функцию многократно, проще использовать выражение:
>>> b = (i*0.33 for i in range(1,4))
>>> b
<generator object <genexpr> at 0x7efc88787960>
>>> for i in b:
... print(i)
...
0.33
0.66
0.99