Что такое list comprehension? Зачем оно? Какие ещё бывают?

Олег

16-й уровень

List comprehension трудно перевести правильно на русский, потому, раз он генерирует новый список, будем называть его просто генератором списков. Это одна из самых приятных вещей в python, научившись писать которую, будешь применять её везде. Как это выглядит? Пожалуй Вы точно видели записи такого вида:

squares = [x ** 2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

это и есть генератор списка, результатом которого будет список квадратов последовательности 0..9. Что здесь происходит? Это обычный цикл for, только записан в удобочитаемом виде, в развёрнутом виде это выглядело бы так:

squares = []
for x in range(10):
  squares.append(x ** 2)
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Вот тут и видна разница в этих записях — генератор списка условно можно назвать синтаксическим сахаром для цикла for, но у них разное время выполнения. Под капотом генератор списка также использует цикл for но выигрывает по скорости из-за того, то не вызывает метод append у списка (подробности здесь).

Так же как и в обычных циклах в генераторах можно применять условия:

>>> odds = [x for x in range(10) if x % 2 != 0]
# [1, 3, 5, 7, 9]

если в условии нужен else, то всё условие пишется до for:

>>> [x ** 2 if x % 2 == 0 else x ** 3 for x in range(10)]
# [0, 1, 4, 27, 16, 125, 36, 343, 64, 729]

Гораздо удобнее итерироваться по двум спискам:

first = []

for x in range(1, 5):
  for y in range(5, 1, -1):
    if x != y:
      first.append((x, y))

second = [(x, y) for x in range(1, 5) for y in range(5, 1, -1) if x != y]

>>> first == second
True

Отличный пример из документации (раскрытие списка списков), усложним его:

>>> vec = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[10, 11, 12], [13, 14, 15], [16, 17, 18]]]
>>> [digit for lst in vec for elem in lst for digit in elem]
# [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]

Подобным образом с помощью генераторов списков мы можем создать словарь:

>>> dict([(key, value) for (key, value) in zip([1, 2, 3], ['a', 'b', 'c'])])
# {1: 'a', 2: 'b', 3: 'c'}

И тут нас ожидает приятная новость — в python есть и генераторы словарей, записываются так же, как и генераторы списков, только в фигурных скобках { ... }:

>>> {key: value for key, value in zip([1, 2, 3], ['a', 'b', 'c'])}
# {1: 'a', 2: 'b', 3: 'c'}

К слову, словарь можно создать и без генератора — dict(zip(list1, list2)).

Если мы генератор списка запишем в круглых скобках, то получим обычный генератор:

>>> gen = (x for x in range(10))
>>> gen
<generator object <genexpr> at 0x7f635076ea98>
>>> gen.__next__()
0
>>> gen.__next__()
1
.......

Официальная документация по list comprehension.