Справочник по Python · модуль 4 · словари, множества и кортежи
Глава 4.11
Вложенные
включения
Вложенность бывает двух разных видов: несколько частей for в одном включении дают плоский результат, а включение внутри выражения — вложенный. Их постоянно путают, хотя решают они противоположные задачи.
- Категория
- Коллекции
- Уровень
- Средний → продвинутый
- Связанные темы
- Включения, вложенные циклы, двумерные списки
- Зачем это знать
- Обрабатывать таблицы и комбинации значений
§1Две разные вложенности
Начать нужно с различия, без которого дальше всё сольётся. Формы похожи, результаты противоположны.
[ x for row in matrix for x in row ] два for подряд └ результат ПЛОСКИЙ: [1, 2, 3, 4, 5, 6] [ [x * 2 for x in row] for row in matrix ] включение внутри └ результат ВЛОЖЕННЫЙ: [[2, 4, 6], [8, 10, 12]]
| Форма | Признак | Результат | Задача |
|---|---|---|---|
несколько for | скобки одни | плоский список | развернуть или скомбинировать |
| включение внутри | вторые скобки слева | список списков | построить таблицу |
Простое правило: сколько пар скобок — столько уровней в результате.
§2Несколько частей for
Части for в одном включении работают как вложенные циклы: каждая следующая перебирается полностью для каждого значения предыдущей.
print([(a, b) for a in "AB" for b in "12"])
[('A', '1'), ('A', '2'), ('B', '1'), ('B', '2')]Тот же результат даёт вложенный цикл из главы 3.7.
result = [] for a in "AB": for b in "12": result.append((a, b)) print(result)
[('A', '1'), ('A', '2'), ('B', '1'), ('B', '2')]§3Порядок перебора
Отсюда правило чтения: части for идут в том же порядке, что вложенные циклы, — слева направо, сверху вниз.
[выражение for a in ... for b in ...]
for a in ...: ← внешний, меняется медленно
for b in ...: ← внутренний, меняется быстро
выражение
Быстрее всего меняется последняя переменная — как в вывод из §2: сначала перебраны обе цифры для A, потом для B.
§4Выпрямление таблицы
Главное применение формы с двумя for — превратить список списков в плоский.
matrix = [[1, 2, 3], [4, 5, 6]] print([x for row in matrix for x in row])
[1, 2, 3, 4, 5, 6]
Читается это так: «для каждой строки в таблице, для каждого элемента в строке — взять элемент». Двумерные списки разбирались в главе 3.13.
§5Порядок нельзя менять
Части for зависят друг от друга: вторая использует переменную первой. Поменять их местами не выйдет.
matrix = [[1, 2], [3, 4]] print([x for x in row for row in matrix])
NameError: name 'row' is not defined
Это частая ошибка: по аналогии с выражением слева хочется поставить «главное» вперёд. Но части for читаются строго слева направо, как вложенные циклы.
§6Комбинации значений
Когда источники независимы, получаются все сочетания.
sizes = ["S", "M"] colors = ["red", "blue"] print([f"{size}-{color}" for size in sizes for color in colors])
['S-red', 'S-blue', 'M-red', 'M-blue']
Число элементов результата — произведение длин: два размера на два цвета дают четыре. С тремя источниками по десять элементов получится уже тысяча, и об этом стоит помнить.
§7Включение внутри выражения
Вторая форма: на месте «что положить» стоит целое включение. Тогда каждый элемент результата сам становится списком.
matrix = [[1, 2, 3], [4, 5, 6]] print([[x * 2 for x in row] for row in matrix])
[[2, 4, 6], [8, 10, 12]]
Сравните с §4: те же данные, почти те же слова, но результат другой. Разница — во вторых квадратных скобках слева.
§8Создание матрицы
Приём из главы 4.10, теперь в контексте вложенности.
rows = [[0] * 3 for _ in range(2)] rows[0][0] = 9 print(rows)
[[9, 0, 0], [0, 0, 0]]
Внутреннюю строку можно построить и включением: [[0 for _ in range(3)] for _ in range(2)]. Результат тот же, запись длиннее — умножение здесь читается лучше.
§9Таблица умножения
Классический пример, где внутреннее включение зависит от внешней переменной.
table = [[i * j for j in range(1, 4)] for i in range(1, 4)] for row in table: print(row)
[1, 2, 3] [2, 4, 6] [3, 6, 9]
Внешнее включение задаёт строки, внутреннее — ячейки внутри строки. Переменная i доступна внутреннему включению, потому что оно вычисляется для каждого значения i заново.
§10Зависимая вложенность
Внутренняя часть может зависеть от внешней и в форме с двумя for — тогда длины строк получатся разными.
print([[i] * i for i in range(1, 4)])
[[1], [2, 2], [3, 3, 3]]
Такие «рваные» таблицы встречаются реже прямоугольных, но включение справляется с ними так же.
§11Условие: где его ставить
С двумя формами появляются и два места для if — и они дают разный результат.
matrix = [[1, 2, 3], [4, 5, 6]] print([x for row in matrix for x in row if x % 2 == 0])
[2, 4, 6]
print([[x for x in row if x % 2 == 0] for row in matrix])
[[2], [4, 6]]
Первая запись теряет разбиение на строки, вторая его сохраняет — и допускает пустые строки, если в какой-то не нашлось подходящих элементов. Выбор зависит от того, нужна ли структура дальше.
§12Три уровня
Ограничений на число частей for нет, но читаемость падает быстро.
data = [[[1, 2]], [[3]]] print([x for a in data for b in a for x in b])
[1, 2, 3]
Три уровня — практический предел. Дальше строка перестаёт читаться, и обычные циклы становятся понятнее.
§13Когда лучше обычные циклы
| Ситуация | Что выбрать |
|---|---|
| выпрямить таблицу | включение с двумя for |
| преобразовать таблицу, сохранив форму | включение внутри включения |
| создать матрицу | включение |
| больше трёх уровней | циклы |
| несколько действий в теле | циклы |
нужен break | циклы |
| строка не помещается на экран | циклы |
Критерий простой: если, чтобы понять включение, приходится мысленно разворачивать его в циклы — лучше сразу написать циклы.
§14Частые ошибки
| Что написано | Что произойдёт | Как правильно |
|---|---|---|
[x for x in row for row in matrix] | NameError: row ещё не определена | [x for row in matrix for x in row] |
два for для сохранения структуры | результат окажется плоским | включение внутри выражения |
| включение внутри для выпрямления | получится список списков | два for в одних скобках |
if в конце при вложенном включении | отберутся строки, а не элементы | поставить if во внутреннее включение |
[[0] * 3] * 2 | строки окажутся одним списком | [[0] * 3 for _ in range(2)] |
| три независимых источника по 100 элементов | миллион комбинаций | оценить размер результата заранее |
| четыре уровня вложенности в одной строке | код нечитаем | развернуть в циклы |
§15Проверьте себя
Как по записи понять, будет результат плоским или вложенным?
По числу пар квадратных скобок: одни скобки с двумя for дают плоский список, вторые скобки слева — список списков.
В каком порядке перебираются части for?
Слева направо, как вложенные циклы: первая часть — внешний цикл, последняя меняется быстрее всех.
Почему [x for x in row for row in matrix] даёт NameError?
Части for читаются слева направо, и row используется раньше, чем объявляется.
Чем отличаются два места для if при вложенности?
if в конце общего включения фильтрует элементы плоского результата. if внутри вложенного включения фильтрует внутри каждой строки, сохраняя структуру.
Сколько элементов даст включение по двум источникам?
Произведение их длин: два размера и два цвета — четыре комбинации.
Когда включение стоит заменить циклами?
Когда уровней больше трёх, в теле нужно несколько действий или требуется break.
§16Шпаргалка
[x for row in m for x in row][[f(x) for x in row] for row in m][(a, b) for a in A for b in B][[0] * n for _ in range(m)][[i * j for j in R] for i in R][x for r in m for x in r if cond][[x for x in r if cond] for r in m]Итог главы
Вложенность бывает двух видов, и различает их число пар скобок. Несколько частей for в одних скобках работают как вложенные циклы и дают плоский результат — это способ выпрямить таблицу или собрать комбинации. Включение внутри выражения даёт список списков и сохраняет структуру. Порядок частей for строго слева направо: переставить их нельзя, потому что каждая следующая опирается на переменную предыдущей. Место для if тоже зависит от формы и определяет, останется ли разбиение на строки.