Перейти к содержимому
Теория · Включения и генераторные выражения · урок 1 из 8

Списковые включения

Чему вы научитесь

Чему вы научитесь:

  • строить новый список из старого одной строкой — списковым включением;
  • отбирать элементы фильтром if и не путать его с условным выражением;
  • собирать включениями словари и множества;
  • считать сумму, минимум и максимум без лишнего списка — генераторным выражением.
Конвейер проводит плитки 1 2 3 4 через пресс n * n, и они выходят как 1 4 9 16

Представьте конвейер на фабрике. По ленте одна за другой едут плитки с числами 1, 2, 3, 4. На пути стоит пресс с надписью n * n: на каждой плитке он выбивает квадрат её числа. В конце ленты в ящик ложатся плитки 1, 4, 9, 16 — в том же порядке, в каком ехали.

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

Python
nums = [1, 2, 3, 4]
squares = []
for n in nums:
    squares.append(n * n)
print(squares)  # [1, 4, 9, 16]

squares = [n * n for n in nums]
print(squares)  # [1, 4, 9, 16]

Давайте разберём обе версии:

  1. Цикл создаёт пустой список squares, перебирает nums и для каждого n добавляет n * n через append.
  2. Строка [n * n for n in nums] делает ту же работу сама: перебирает nums и собирает квадраты в новый список.
  3. Обе версии печатают [1, 4, 9, 16].

Запись [n * n for n in nums] называют списковым включением. Она читается почти как фраза: «n * n для каждого n из nums». В ней нет пустого списка и append, а имя squares встречается один раз.

Как устроено включение

Включение повторяет конвейер. Внутри квадратных скобок две части:

  • сначала выражение — пресс, который делает из элемента новое значение;
  • затем заголовок цикла for без двоеточия — лента, которая подаёт элементы по одному.

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

Включение — выражение, как и условное выражение. Его можно присвоить переменной, передать в print или поставить после return. Перебирать можно любой итерируемый объект — список, строку, range:

Python
print([c.upper() for c in "abc"])  # ['A', 'B', 'C']
print([i * 10 for i in range(4)])  # [0, 10, 20, 30]
print([len(w) for w in ["ox", "sun"]])  # [2, 3]

Условие-фильтр

Перед прессом можно поставить контролёра: он пропускает дальше только подходящие плитки. Во включении эту роль играет if с условием после заголовка for. В результат попадают только те элементы, для которых условие истинно:

Python
nums = [4, -1, 7, 0, -3]
print([n * 2 for n in nums if n > 0])  # [8, 14]
print([n if n > 0 else 0 for n in nums])  # [4, 0, 7, 0, 0]

Разберём первую строку:

  1. for n in nums подаёт числа по одному: 4, -1, 7, 0, -3.
  2. if n > 0 пропускает дальше только 4 и 7.
  3. Выражение n * 2 вычисляется лишь для них, и в список попадают 8 и 14.

Элемент, который не прошёл проверку, выпадает целиком, поэтому результат короче исходного списка. Такое включение заменяет цикл, в котором append стоит внутри if.

Во второй строке фильтра нет: на месте выражения стоит условное выражение n if n > 0 else 0. Оно выбирает значение для каждого элемента, и длина списка сохраняется. Все числа, кроме положительных, заменены на 0, а не выброшены.

Цикл наизнанку

Самое непривычное во включении — порядок. Читают его слева направо, а работает оно в другом порядке:

  1. Сначала for берёт очередной элемент.
  2. Затем if решает, оставить ли его.
  3. Последним вычисляется выражение слева, и его значение добавляется в список.

Включение — это цикл с append, вывернутый наизнанку. То, что в цикле стоит в самой глубине, append(n * 2), во включении вынесено вперёд. Нажмите «Следующий шаг» и следите, в каком порядке работают строки цикла: включение в последней строке проходит те же шаги.

ВживуюЦикл с append и то же включениешаг 1 из 12 · рамок: 1

Список nums — лента с тремя плитками.

Следите, в каком порядке срабатывают for, if и n * 2

У включения есть ещё одно отличие от цикла: его переменная живёт только внутри скобок. После цикла for x in nums имя x остаётся со значением последнего элемента. После [x * 2 for x in nums] имени x снаружи нет, и print(x) сообщит об ошибке NameError. Этот вопрос часто задают на собеседовании.

Два цикла for

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

  • первый for — внешний;
  • второй for — внутренний, и он может перебирать переменную внешнего.

Так [ch for w in ["ab", "cd"] for ch in w] даёт ['a', 'b', 'c', 'd']. Внешний цикл берёт слово, а внутренний — его буквы. Это тот же результат, что у цикла for w in …, внутри которого цикл for ch in w добавляет ch через append.

Порядок заголовков обратен фразе «буквы каждого слова». В записи [ch for ch in w for w in words] имени w ещё нет, и Python сообщает об ошибке NameError. Когда в одной строке два for и условие, понятнее обычные вложенные циклы.

Частая ошибка

Фильтр написали перед for, как условное выражение без else:

Python
nums = [4, -1, 7, 0, -3]
positive = [n if n > 0 for n in nums]  # ✗
TEXT
SyntaxError: expected 'else' after 'if' expression

Перед for Python ждёт выражение, а n if n > 0 без else выражением не является, поэтому файл не запускается. Условие, которое отбрасывает элементы, пишут после заголовка цикла: [n for n in nums if n > 0].

Главное

  • Списковое включение [n * n for n in nums] строит новый список, а исходный не меняет.
  • Фильтр if стоит после for и выбрасывает элементы; условное выражение с else стоит перед for и сохраняет длину.
  • Включение — цикл с append наизнанку: сначала for, затем if, последним — выражение слева.
  • Несколько for вложены в порядке записи: первый — внешний.

Подпишись, чтобы пройти курс

Курс бесплатный. Войди через Telegram и подпишись на канал — откроются все уроки.

Войти другим способом