Генераторы списков в Python

В языке программирования Python существует специальная синтаксическая конструкция, позволяющая создавать заполненные списки по определенным правилам. Создаваемые списки могут быть разными, содержание конструкции немного отличаться, поэтому такие конструкции называют генераторами списков. Их удобство заключается в более короткой записи, чем если создавать список обычным способом.

Например, надо создать список, заполненный натуральными числами до определенного числа. "Классический" способ будет выглядеть так:

>>> a = []
>>> for i in range(1,15):
...     a.append(i)
... 
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

Создание списка заняло у нас три строчки кода. Генератор же списка сделает это за одну:

>>> a = [i for i in range(1,15)]
>>> a
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

В данном случае конструкция [i for i in range(1,15)] является генератором списка. Вся конструкция заключается в квадратные скобки, что как бы говорит, что будет создан список. Внутри квадратных скобок можно выделить три части: 1) что делаем с элементом (в данном случае ничего не делаем, просто добавляем в список), 2) что берем (в данном случае элемент i), 3) откуда берем (здесь из объекта range). Части отделены друг от друга ключевыми словами for и in.

Рассмотрим такой пример:

>>> a = [2,-2,4,-4,7,5]
>>> b = [i**2 for i in a]
>>> b
[4, 4, 16, 16, 49, 25]

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

>>> a = {1:10, 2:20, 3:30}
>>> b = [i*a[i] for i in a]
>>> b
[10, 40, 90]

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

>>> a = {1:10, 2:20, 3:30}
>>> b = [[i,a[i]] for i in a]
>>> b
[[1, 10], [2, 20], [3, 30]]
>>> c = [j for i in b for j in i]
>>> c
[1, 10, 2, 20, 3, 30]

В этом примере список b состоит из вложенных списков. Если бы в генераторе мы опустили квадратные скобки в выражении [i,a[i]], то произошла бы ошибка. Если все же надо получить одноуровневый список из ключей и значений словаря, надо взять каждый вложенный список и из него взять каждый элемент. Это достигается за счет вложенной конструкции for. "Классический" синтаксис для заполнения списка c выглядел бы так:

>>> c = []
>>> for i in b:
...     for j in i:
...             c.append(j)
... 
>>> c
[1, 10, 2, 20, 3, 30]

В генераторы списков в конце можно добавлять конструкцию if. Например, надо из строки извлечь все цифры:

>>> a = "lsj94ksd231 9"
>>> b = [int(i) for i in a if '0'<=i<='9']
>>> b
[9, 4, 2, 3, 1, 9]

Или заполнить список числами, кратными 30 или 31:

>>> a = [i for i in range(30,250) if i%30 == 0 or i%31 == 0]
>>> a
[30, 31, 60, 62, 90, 93, 120, 124, 150, 155, 180, 186, 210, 217, 240, 248]

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

Если следовать терминологии,

Если следовать терминологии, то это всё-таки не генераторы. И в функциональном программировании, и в python генераторы выглядят немного иначе, круглые скобки, а не квадратные:

(i for i in range(5))

А с квадратными - как их только не переводили, но наиболее популярное название на русском - [b]списочные выражения[/i]

Разница с генераторами в том, что генертатор - это генератор :), а это - это уже готовый список:

>>> [i for i in range(5)]
[0, 1, 2, 3, 4]
>>> (i for i in range(5))
<generator object <genexpr> at 0x2dfb81b2640>

если их использовать, как итераторы - то разницы нет:

a = (i for i in range(5))
b = [i for i in range(5)]
 
for i in a:
    print (i)
 
for i in b:
    print (i)

- дадут абсолютно одинаковый ответ. А разница в том, что первое генерируется по запросу, а второе - это уже полноценный список, который сформирован.

def motor(s):
    print s
 
a = (motor(i) for i in range(5))
 
b = [motor(i) for i in range(5,10)]

... выдаст 5 6 7 8 9.

a.next()
a.next()

- это обращение к генератору. Взяты два первых элемента. И теперь:

for i in a:
    pass

пройдёт уже не 5 раз, а только по трём оставшимся. Если обратиться ещё раз - то не пройдёт ни по какому, потому что элементы - закончились.

Но, собственно, элементов там никаких и нет. Если обратиться к b[0], то он выдаст первый элемент списка. А a[0] - вызовет ошибку, потому что это не список, и пройтись по нему можно только итерацией, и количество элементов в генераторе - неизвестно. Можно превратить генератор в список через a = list(a), тогда a и b будут идентичными списками.