Модуль 4 · Словари, множества и кортежи

Справочник по Python · модуль 4 · словари, множества и кортежи

Глава 4.10

4.10

Включения
в Python

Включение собирает новую коллекцию из существующей одним выражением. Это не «короткая запись цикла ради краткости»: включение сразу объявляет намерение — «получить новый список из этих элементов», — и потому читается быстрее, чем цикл с накоплением.

Категория
Коллекции
Уровень
Средний
Связанные темы
Циклы, списки, словари, множества
Зачем это знать
Строить коллекции коротко и читаемо

§1От цикла к включению

Задача «построить новый список на основе существующего» решается циклом с накоплением — приёмом из главы 3.10.

main.pyпривычный способ
squares = []

for number in range(1, 6):
    squares.append(number * number)

print(squares)
Выводпять квадратов
[1, 4, 9, 16, 25]

Здесь четыре строки, из которых по-настоящему содержательна одна — number * number. Остальные — обвязка: создать пустой список, открыть цикл, вызвать append(). Включение убирает обвязку.

main.pyто же самое одним выражением
squares = [number * number for number in range(1, 6)]

print(squares)
Выводрезультат тот же
[1, 4, 9, 16, 25]

§2Строение включения

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

Схемаиз чего состоит запись
[ number * number   for number in range(1, 6) ]
  └─────┬───────┘   └──────────┬────────────┘
   что положить          откуда брать
   в новый список        элементы

Читать удобно с середины: сначала часть for, затем выражение слева. «Для каждого number из диапазона — положить number * number».

§3Преобразование элементов

Самое частое применение — применить к каждому элементу одно и то же действие.

main.pyдва преобразования одного списка
words = ["python", "sql", "go"]

print([word.upper() for word in words])
print([len(word) for word in words])
Выводдлина результата совпадает с исходной
['PYTHON', 'SQL', 'GO']
[6, 3, 2]

Без фильтрации включение всегда даёт ровно столько элементов, сколько было на входе. Исходный список при этом не меняется — создаётся новый.

§4Фильтрация через if

Необязательная часть if в конце отбирает, какие элементы вообще попадут в результат.

main.pyтолько чётные числа
print([number for number in range(10) if number % 2 == 0])
Выводпять элементов из десяти
[0, 2, 4, 6, 8]
Схемаполная форма
[ выражение   for имя in источник   if условие ]
      │                  │               │
   что положить      откуда брать    что пропустить

Теперь длина результата может быть меньше исходной. Порядок частей строгий: сначала for, потом if.

§5Два if подряд

Условий может быть несколько — тогда элемент должен пройти все.

main.pyделится и на 2, и на 3
print([n for n in range(20) if n % 2 == 0 if n % 3 == 0])
Выводчисла, кратные шести
[0, 6, 12, 18]

Два if подряд равнозначны одному с and. Выбор — вопрос читаемости: когда условия логически независимы, раздельная запись понятнее.

§6Условие в результате

Есть и другой if — не отбирающий, а выбирающий значение. Он ставится слева, в части «что положить», и обязательно с else.

main.pyтернарный оператор внутри включения
print(["чёт" if n % 2 == 0 else "нечет" for n in range(4)])
Выводэлементов столько же, сколько на входе
['чёт', 'нечет', 'чёт', 'нечет']

Это тернарный оператор из главы 2.6, поставленный на место выражения. Он ничего не отбрасывает — только решает, какое значение положить.

§7Два разных if

Два if из §5 и §6 выглядят похоже, но делают противоположное. Различить их можно по позиции и по наличию else.

Где стоитЕсть elseЧто делаетДлина результата
в конце, после forнетотбирает элементыможет уменьшиться
в начале, до forда, обязательновыбирает значениене меняется
Сравнениеодни и те же слова, разный смысл
[n for n in data if n > 0]              оставить только положительные
[n if n > 0 else 0 for n in data]       отрицательные заменить нулём

Первая запись сокращает список, вторая сохраняет его длину. Требование else в левой форме не случайно: если условие ложно, что-то же надо положить в результат.

§8Включение множества

Скобки определяют тип результата. Фигурные с одним выражением дают множество — со всеми его свойствами из главы 4.5.

main.pyуникальные длины слов
words = ["python", "sql", "go"]

print({len(word) for word in words})
Выводповторы отсеялись бы сами
{2, 3, 6}

Слов три, длин тоже три — но если бы среди слов нашлись одинаковые по длине, в множестве осталась бы одна запись.

§9Включение словаря

Те же фигурные скобки, но выражение содержит пару через двоеточие.

main.pyслово и его длина
words = ["python", "sql", "go"]

print({word: len(word) for word in words})
Выводготовый словарь
{'python': 6, 'sql': 3, 'go': 2}

Именно эта форма решала задачу из главы 4.3: {key: [] for key in keys} создаёт по независимому списку на каждый ключ, в отличие от dict.fromkeys().

§10Обращение словаря

Частая задача — поменять ключи и значения местами. Со словарным включением это одна строка.

main.pyцена становится ключом
prices = {"book": 900, "pen": 120}

print({price: name for name, price in prices.items()})
Выводпары перевёрнуты
{900: 'book', 120: 'pen'}

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

§11Скобки решают тип

ЗаписьТип результата
[x for x in data]list
{x for x in data}set
{k: v for k, v in pairs}dict
(x for x in data)генератор, не кортеж

Последняя строка — исключение из правила: круглые скобки кортеж не создают. Чтобы получить кортеж, результат оборачивают: tuple(x for x in data).

§12Генераторное выражение

Круглые скобки дают генератор — объект, который вычисляет элементы по одному при обходе, а не строит список целиком.

main.pyчто именно получилось
squares = (n * n for n in range(5))

print(type(squares).__name__)
print(list(squares))
Выводсписок получен только по требованию
generator
[0, 1, 4, 9, 16]

Практическая польза — экономия памяти, когда результат нужен один раз. Запись sum(len(w) for w in words) не создаёт промежуточный список: скобки генератора можно опустить, если он единственный аргумент функции.

main.pyсуммирование без промежуточного списка
words = ["python", "sql", "go"]

print(sum(len(word) for word in words))
Выводобщее число символов
11

Генераторы подробно разбираются в главе 7.2.

§13Область видимости

Переменная включения живёт только внутри него и наружу не выходит.

main.pyобращение к x после включения
values = [x for x in range(3)]
print(x)
Ошибкаимени не существует
NameError: name 'x' is not defined

Это отличие от обычного цикла, где переменная остаётся доступной после завершения. Свойство полезное: включение не засоряет окружающий код и не может случайно затереть существующую переменную с тем же именем.

§14Независимые списки

Одна задача, где включение не просто короче, а единственно верно, — создание списка списков.

main.pyумножение списка вместо включения
rows = [[0] * 3] * 2
rows[0][0] = 9

print(rows)
Выводизменились обе строки
[[9, 0, 0], [9, 0, 0]]
main.pyто же самое включением
rows = [[0] * 3 for _ in range(2)]
rows[0][0] = 9

print(rows)
Выводстроки независимы
[[9, 0, 0], [0, 0, 0]]

Причина та же, что у dict.fromkeys() в главе 4.3: умножение копирует ссылку на один и тот же список, а включение выполняет выражение заново на каждом шаге и создаёт новый список. Подчёркивание в роли переменной — приём из главы 6.7.

§15Когда нужен обычный цикл

Включение — не всегда лучший выбор. Есть случаи, где цикл понятнее.

СитуацияЧто использовать
построить новую коллекцию из существующейвключение
в теле несколько действийцикл
нужен break или continueцикл
результат не нужен, важен побочный эффектцикл
условий и вложенности так много, что строка не читаетсяцикл

Четвёртая строка про частую ошибку стиля: включение ради одного print() создаёт список из None, который тут же выбрасывается. Если результат не нужен — нужен цикл.

§16Частые ошибки

Что написаноЧто произойдётКак правильно
[n if n > 0 for n in data]SyntaxError: слева нужен else[n for n in data if n > 0]
[n for n in data if n > 0 else 0]SyntaxError: справа else не бывает[n if n > 0 else 0 for n in data]
(x for x in data) для кортежаполучится генераторtuple(x for x in data)
обратиться к переменной после включенияNameErrorсохранить нужное значение отдельно
[[0] * 3] * 2строки окажутся одним списком[[0] * 3 for _ in range(2)]
[print(x) for x in data]создастся список из Noneобычный цикл
обратить словарь с повторяющимися значениямичасть пар потеряетсяпроверить уникальность значений
включение в несколько строк с тремя условиямичитается хуже цикларазвернуть в цикл

§17Проверьте себя

В каком порядке читать включение?

С середины: сначала часть for — откуда берутся элементы, затем выражение слева — что кладётся в результат.

Чем отличаются два if во включении?

if в конце отбирает элементы и может уменьшить длину результата. if ... else слева выбирает значение и длину не меняет.

Почему в левой форме else обязателен?

Потому что это тернарный оператор: если условие ложно, в результат всё равно нужно что-то положить.

Что создаст (x for x in data)?

Генератор, а не кортеж. Для кортежа нужно tuple(x for x in data).

Доступна ли переменная включения после него?

Нет, обращение к ней даёт NameError. В обычном цикле переменная сохраняется.

Почему [[0] * 3] * 2 — ошибка?

Умножение копирует ссылку на один список, и обе строки окажутся одним объектом. Включение создаёт новый список на каждом шаге.

§18Шпаргалка

Список[f(x) for x in data]
С отбором[x for x in data if cond]
С заменой[x if cond else 0 for x in data]
Множество{f(x) for x in data}
Словарь{k: v for k, v in pairs}
Обратить словарь{v: k for k, v in d.items()}
Генераторsum(f(x) for x in data)
Матрица[[0] * n for _ in range(m)]

Итог главы

Включение строит новую коллекцию одним выражением, объявляя намерение раньше механики. Тип результата задают скобки: квадратные дают список, фигурные — множество или словарь, круглые — генератор, а не кортеж. Два вида if различаются позицией: справа он отбирает элементы, слева выбирает значение и требует else. Переменная включения наружу не выходит, а само включение — единственный корректный способ создать список независимых списков.

Глава 4.10 · Включения · Справочник по Python Автор: Макаров Максим Николаевич