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

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

Глава 4.11

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 в одном включении работают как вложенные циклы: каждая следующая перебирается полностью для каждого значения предыдущей.

main.pyдве буквы и две цифры
print([(a, b) for a in "AB" for b in "12"])
Выводчетыре пары
[('A', '1'), ('A', '2'), ('B', '1'), ('B', '2')]

Тот же результат даёт вложенный цикл из главы 3.7.

main.pyразвёрнутая запись
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 — превратить список списков в плоский.

main.pyиз таблицы в один список
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 зависят друг от друга: вторая использует переменную первой. Поменять их местами не выйдет.

main.pyrow используется до объявления
matrix = [[1, 2], [3, 4]]

print([x for x in row for row in matrix])
Ошибкаимени ещё не существует
NameError: name 'row' is not defined

Это частая ошибка: по аналогии с выражением слева хочется поставить «главное» вперёд. Но части for читаются строго слева направо, как вложенные циклы.

§6Комбинации значений

Когда источники независимы, получаются все сочетания.

main.pyразмеры и цвета
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Включение внутри выражения

Вторая форма: на месте «что положить» стоит целое включение. Тогда каждый элемент результата сам становится списком.

main.pyудвоить каждое число, сохранив структуру
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, теперь в контексте вложенности.

main.pyтаблица из нулей
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Таблица умножения

Классический пример, где внутреннее включение зависит от внешней переменной.

main.pyтри на три
table = [[i * j for j in range(1, 4)] for i in range(1, 4)]

for row in table:
    print(row)
Выводкаждая строка — произведения на i
[1, 2, 3]
[2, 4, 6]
[3, 6, 9]

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

§10Зависимая вложенность

Внутренняя часть может зависеть от внешней и в форме с двумя for — тогда длины строк получатся разными.

main.pyстрока длиной i
print([[i] * i for i in range(1, 4)])
Выводступенчатая структура
[[1], [2, 2], [3, 3, 3]]

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

§11Условие: где его ставить

С двумя формами появляются и два места для if — и они дают разный результат.

main.pyотбор при выпрямлении
matrix = [[1, 2, 3], [4, 5, 6]]

print([x for row in matrix for x in row if x % 2 == 0])
Выводплоский список чётных
[2, 4, 6]
main.pyотбор внутри каждой строки
print([[x for x in row if x % 2 == 0] for row in matrix])
Выводструктура сохранена
[[2], [4, 6]]

Первая запись теряет разбиение на строки, вторая его сохраняет — и допускает пустые строки, если в какой-то не нашлось подходящих элементов. Выбор зависит от того, нужна ли структура дальше.

§12Три уровня

Ограничений на число частей for нет, но читаемость падает быстро.

main.pyсписок списков списков
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 тоже зависит от формы и определяет, останется ли разбиение на строки.

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