Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.10
Включения
в Python
Включение собирает новую коллекцию из существующей одним выражением. Это не «короткая запись цикла ради краткости»: включение сразу объявляет намерение — «получить новый список из этих элементов», — и потому читается быстрее, чем цикл с накоплением.
- Категория
- Коллекции
- Уровень
- Средний
- Связанные темы
- Циклы, списки, словари, множества
- Зачем это знать
- Строить коллекции коротко и читаемо
§1От цикла к включению
Задача «построить новый список на основе существующего» решается циклом с накоплением — приёмом из главы 3.10.
squares = [] for number in range(1, 6): squares.append(number * number) print(squares)
[1, 4, 9, 16, 25]
Здесь четыре строки, из которых по-настоящему содержательна одна — number * number. Остальные — обвязка: создать пустой список, открыть цикл, вызвать append(). Включение убирает обвязку.
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Преобразование элементов
Самое частое применение — применить к каждому элементу одно и то же действие.
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 в конце отбирает, какие элементы вообще попадут в результат.
print([number for number in range(10) if number % 2 == 0])
[0, 2, 4, 6, 8]
[ выражение for имя in источник if условие ]
│ │ │
что положить откуда брать что пропустить
Теперь длина результата может быть меньше исходной. Порядок частей строгий: сначала for, потом if.
§5Два if подряд
Условий может быть несколько — тогда элемент должен пройти все.
print([n for n in range(20) if n % 2 == 0 if n % 3 == 0])
[0, 6, 12, 18]
Два if подряд равнозначны одному с and. Выбор — вопрос читаемости: когда условия логически независимы, раздельная запись понятнее.
§6Условие в результате
Есть и другой if — не отбирающий, а выбирающий значение. Он ставится слева, в части «что положить», и обязательно с else.
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.
words = ["python", "sql", "go"] print({len(word) for word in words})
{2, 3, 6}Слов три, длин тоже три — но если бы среди слов нашлись одинаковые по длине, в множестве осталась бы одна запись.
§9Включение словаря
Те же фигурные скобки, но выражение содержит пару через двоеточие.
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Обращение словаря
Частая задача — поменять ключи и значения местами. Со словарным включением это одна строка.
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Генераторное выражение
Круглые скобки дают генератор — объект, который вычисляет элементы по одному при обходе, а не строит список целиком.
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) не создаёт промежуточный список: скобки генератора можно опустить, если он единственный аргумент функции.
words = ["python", "sql", "go"] print(sum(len(word) for word in words))
11
Генераторы подробно разбираются в главе 7.2.
§13Область видимости
Переменная включения живёт только внутри него и наружу не выходит.
values = [x for x in range(3)] print(x)
NameError: name 'x' is not defined
Это отличие от обычного цикла, где переменная остаётся доступной после завершения. Свойство полезное: включение не засоряет окружающий код и не может случайно затереть существующую переменную с тем же именем.
§14Независимые списки
Одна задача, где включение не просто короче, а единственно верно, — создание списка списков.
rows = [[0] * 3] * 2 rows[0][0] = 9 print(rows)
[[9, 0, 0], [9, 0, 0]]
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. Переменная включения наружу не выходит, а само включение — единственный корректный способ создать список независимых списков.