Цикл for наиболее часто используется для обхода вложенных списков и добавления их элементов в новый список с помощью метода .extend() или оператора +=
Стандартная библиотека Python предлагает и другие инструменты для той же задачи. Включения списков (list comprehensions) дают лаконичное однострочное решение. У каждого метода свои характеристики производительности, однако циклы for и включения списков, как правило, работают быстрее
После изучения этого руководства вы будете понимать следующее:
- Выравнивание списка — это преобразование вложенных списков в единый список
- Для выравнивания подходят цикл
forс.extend()или включение списка - Функции стандартной библиотеки —
itertools.chain()иfunctools.reduce()— тоже справляются с задачей - Пользовательская функция
flatten(), рекурсивная или итеративная, обрабатывает произвольно вложенные списки - Метод
.flatten()в NumPy эффективно выравнивает массивы для задач науки о данных
Чтобы лучше проиллюстрировать, что означает выравнивание, предположим, что у вас есть следующая матрица числовых значений:
>>> matrix = [
... [ 9 , 3 , 8 , 3 ],
... [ 4 , 5 , 2 , 8 ],
... [ 6 , 4 , 3 , 1 ],
... [ 1 , 0 , 4 , 5 ],
... ]
Переменная matrix содержит список Python, включающий четыре вложенных списка. Каждый вложенный список представляет строку матрицы, в каждой строке хранится по четыре элемента. Теперь предположим, что нужно превратить эту матрицу в следующий список:
[ 9 , 3 , 8 , 3 , 4 , 5 , 2 , 8 , 6 , 4 , 3 , 1 , 1 , 0 , 4 , 5 ]
Как выровнять матрицу и получить одномерный список? В этом руководстве вы узнаете, как сделать это в Python
В Python функция
itertools.chain()удобна для объединения итерируемых объектов. Если вы хотите узнать больше, как использовать эту функцию, переходите по ссылке
Flexbox предоставляет мощные инструменты для создания гибких макетов на сайте. Ознакомьтесь с полным руководством по Flexbox для более глубокого понимания
Если вы хотите разобраться в CSS-фреймворках и их применении, прочитайте статью о CSS-фреймворках
- Выравнивание списка с помощью цикла for
- Включения списков для выравнивания
- Выравнивание списка с помощью инструментов стандартной библиотеки и встроенных средств
- Объединение итерируемых объектов с помощью itertools.chain()
- Конкатенация списков с помощью functools.reduce()
- Использование sum() для конкатенации списков
- Выравнивание произвольно вложенных списков
- Использование рекурсии
- Использование цикла и стека
- Оценка производительности при выравнивании списков
- Выравнивание списков для науки о данных с помощью NumPy
- Ответы на эти вопросы могут быть для вас полезными
Выравнивание списка с помощью цикла for
Чтобы выровнять список списков, нужно выполнить несколько шагов — явно или неявно:
- Создать новый пустой список для хранения выровненных данных
- Перебрать каждый вложенный список или подсписок в исходном списке
- Добавить каждый элемент текущего подсписка в список выровненных данных
- Вернуть результирующий список
Существует несколько подходов к преобразованию вложенных списков в одномерные. Наиболее очевидный и читаемый — использование цикла for, который эффективно перебирает подсписки
Для добавления элементов в новый плоский список есть несколько допустимых вариантов. Сначала рассмотрим метод .extend() из класса list, а затем оператор расширенной конкатенации (+=)
Продолжая пример с матрицей, вот как перевести эти шаги в код Python с циклом for и методом .extend():
>>> def flatten_extend ( matrix ):
... flat_list = []
... for row in matrix :
... flat_list . extend ( row )
... return flat_list
...
Внутри flatten_extend() сначала создаётся новый пустой список flat_list — он будет хранить выровненные данные, извлечённые из matrix. Затем запускается цикл для перебора вложенных списков из matrix. Имя row обозначает текущий вложенный список
На каждой итерации .extend() добавляет содержимое текущего подсписка в flat_list. Этот метод принимает итерируемый объект в качестве аргумента и добавляет его элементы в конец целевого списка
Выполните следующий код, чтобы убедиться, что функция работает правильно:
>>> flatten_extend ( matrix )
[9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
Список выровнен. В результате получился одномерный список, содержащий все числовые значения из matrix
Используя метод .extend(), мы получаем более удобный и понятный способ для выравнивания списков. Оператор расширенной конкатенации (+=) также позволяет добиться аналогичного результата, хотя он может быть менее удобен для чтения
>>> def flatten_concatenation ( matrix ):
... flat_list = []
... for row in matrix :
... flat_list += row
... return flat_list
...
Эта функция похожа на flatten_extend(). Единственное отличие — вызов .extend() заменён расширенной конкатенацией, которая добавляет список элементов в конец существующего списка
Вызовите функцию с matrix в качестве аргумента:
>>> flatten_concatenation ( matrix )
[9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
Вызов flatten_concatenation() возвращает тот же результат, что и flatten_extend(). Обе функции эквивалентны и взаимозаменяемы — их цель одна: выровнять список списков. Выбор между ними остаётся за вами, однако с точки зрения читаемости flatten_extend() выглядит предпочтительнее
Примечание: во всех примерах этого руководства в качестве входных данных используется один и тот же список списков — matrix. Однако примеры одинаково хорошо работают с вложенными списками произвольных объектов, списками, смешивающими разные объекты, и вложенными списками разной длины
Техники из следующих разделов рассчитаны на один уровень вложенности. Если ваши данные имеют более глубокую или непоследовательную вложенность, обратитесь к разделу о выравнивании произвольно вложенных списков
Теперь, когда базовый подход понятен, стоит изучить другие варианты решения той же задачи — это поможет выбрать правильный инструмент для каждой конкретной ситуации
После изучения всех подходов вы проведёте тест производительности, чтобы сравнить время выполнения каждого из них. Это даст актуальные данные для случаев, когда производительность кода критически важна
Включения списков для выравнивания
Включения списков (list comprehensions) — отличительная особенность Python. Они весьма популярны в сообществе Python, поэтому вы наверняка встретите их во многих кодовых базах. Включения позволяют быстро создавать и преобразовывать списки с помощью синтаксиса, напоминающего цикл for, но умещающегося в одну строку
Базовый синтаксис включения списка выглядит так:
[ expression ( item ) for item in iterable ]
Каждое включение списка требует как минимум трёх компонентов:
expression()— выражение Python, возвращающее конкретное значение; в большинстве случаев оно зависит отitemitem— текущий объект изiterableiterable— любой итерируемый объект Python:list,tuple,set, строка или генератор
Конструкция for перебирает элементы в iterable, а expression(item) предоставляет соответствующий элемент для нового списка. Включения также могут содержать вложенные конструкции for и условные операторы — в этом руководстве мы будем использовать именно вложенные конструкции
Включение списка можно использовать для выравнивания списка списков. Функция ниже показывает, как это делается:
>>> def flatten_comprehension ( matrix ):
... return [ item for row in matrix for item in row ]
...
Это включение содержит две вложенные конструкции for. Первая перебирает строки в matrix — вашем списке списков. Вторая перебирает элементы в каждой строке. Выражение здесь относительно простое: нужно лишь извлечь элементы из каждого подсписка
Вот как функция работает на практике:
>>> matrix = [
... [ 9 , 3 , 8 , 3 ],
... [ 4 , 5 , 2 , 8 ],
... [ 6 , 4 , 3 , 1 ],
... [ 1 , 0 , 4 , 5 ],
... ]
>>> flatten_comprehension ( matrix )
[9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
Вызов flatten_comprehension() обрабатывает содержимое matrix, выравнивает его и возвращает новый одномерный список с исходными данными
Включения весьма популярны в Python — они позволяют создавать новые списки из существующих итерируемых объектов. По сути, это лаконичные циклы for, которые помогают быстро преобразовывать данные и получать новый список в результате
Однако включения — не единственный альтернативный инструмент для выравнивания списка списков. Стандартная библиотека Python предлагает ещё несколько вариантов, способных помочь с этой задачей, и полезные решения можно найти даже среди встроенных средств
Выравнивание списка с помощью инструментов стандартной библиотеки и встроенных средств
Помимо ручных подходов, в Python есть несколько готовых инструментов — как в стандартной библиотеке, так и среди встроенных функций. Для выравнивания списка списков можно использовать любой из них:
- функцию
chain()из модуляitertools - функцию
reduce()из модуляfunctools - встроенную функцию
sum()
В следующих разделах разберём, как каждый из этих инструментов решает задачу выравнивания
Объединение итерируемых объектов с помощью itertools.chain()
Функция chain() объединяет несколько итерируемых объектов (iterables) в один — именно это и следует из её названия. Вместо готового списка она возвращает итератор, который последовательно выдаёт элементы из всех входных итерируемых объектов до их исчерпания
Совместно с list() функция chain() позволяет выровнять список списков. Вот как это выглядит на практике:
>>> from itertools import chain
>>> def flatten_chain(matrix):
... return list(chain.from_iterable(matrix))
...
>>> matrix = [
... [9, 3, 8, 3],
... [4, 5, 2, 8],
... [6, 4, 3, 1],
... [1, 0, 4, 5],
... ]
>>> flatten_chain(matrix)
[9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
Сначала импортируется chain() из модуля itertools. Важный нюанс: chain реализован не как обычная функция, а как класс — именно поэтому у него есть метод класса .from_iterable(). Этот метод служит альтернативным конструктором: он принимает итерируемый объект итерируемых объектов, то есть ему можно напрямую передать список списков
Последний шаг — построить список из итератора, который возвращает .from_iterable(). Для этого вызывается list(), который потребляет итератор и сохраняет данные в новом списке. При вызове flatten_chain() с matrix в качестве аргумента получаем выровненный список — именно то, что нужно
Мне особенно нравится это решение за читаемость: его можно воспринять почти как обычный текст — объединить строки matrix в один итерируемый объект, затем преобразовать в список
Конкатенация списков с помощью functools.reduce()
Функция reduce() из модуля functools — ещё один инструмент для выравнивания списков. Она входит в набор средств функционального программирования Python: принимает функцию с двумя аргументами, возвращающую единственное значение, и последовательно применяет её к элементам итерируемого объекта
Механика такая: reduce() берёт пару элементов и вычисляет промежуточный результат, затем использует этот результат вместе со следующим элементом для вычисления следующего промежуточного значения. Так формируется неявный аккумулятор, хранящий накопленное значение на каждом шаге
С reduce() можно использовать разные функциональные объекты. В примере ниже применяется пользовательская лямбда-функция:
>>> from functools import reduce
>>> def flatten_reduce_lambda(matrix):
... return list(reduce(lambda x, y: x + y, matrix, []))
...
>>> matrix = [
... [9, 3, 8, 3],
... [4, 5, 2, 8],
... [6, 4, 3, 1],
... [1, 0, 4, 5],
... ]
>>> flatten_reduce_lambda(matrix)
[9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
Первый аргумент reduce() — лямбда-функция, принимающая два аргумента x и y и возвращающая их сумму. Вторым аргументом передаётся matrix, третьим — пустой список, задающий начальное значение для вычислений
Внутри reduce() выполняет конкатенацию списков: начальный пустой список последовательно объединяется со строками matrix. Конечный результат — нужный выровненный список
Помимо лямбды, для того же результата можно использовать несколько функций из модуля operator:
add()— суммирует два числа, эквивалентна оператору сложения (+)concat()— конкатенирует два значения, эквивалентна оператору конкатенации для списков (+)iconcat()— конкатенирует два значения на месте, эквивалентна оператору расширенной конкатенации (+=)
Чтобы использовать одну из этих функций в качестве аргумента reduce(), достаточно заменить лямбду в примере выше на нужную функцию — попробуйте сделать это самостоятельно
Использование sum() для конкатенации списков
Встроенная функция sum() — ещё один способ выровнять список списков. На первый взгляд такое применение sum() выглядит неожиданно: это не самое читаемое решение, но оно работает, и в чужом коде его вполне можно встретить:
>>> def flatten_sum(matrix):
... return sum(matrix, [])
...
>>> matrix = [
... [9, 3, 8, 3],
... [4, 5, 2, 8],
... [6, 4, 3, 1],
... [1, 0, 4, 5],
... ]
>>> flatten_sum(matrix)
[9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5]
Здесь sum() конкатенирует подсписки из matrix. Обратите внимание: для корректной работы обязательно нужно передать пустой список вторым аргументом — он задаёт начальное значение для конкатенации
Даже если sum() не оптимизирована для операций такого типа, она даёт быстрое однострочное решение без импортов и явных циклов — и тем самым экономит время на обдумывание и написание кода
Выравнивание произвольно вложенных списков
До сих пор во всех примерах предполагалось, что список имеет один уровень вложенности, как matrix. В реальных задачах вложенность бывает нерегулярной. Представьте разобранный JSON-документ, дерево каталогов или список результатов поиска, где одни элементы сгруппированы во вложенные списки, а другие — нет
Для выравнивания таких данных есть два естественных подхода: рекурсия или итерация со стеком
Использование рекурсии
Рекурсивный подход предполагает функцию, которая вызывает саму себя для каждого вложенного списка — до тех пор, пока не выровняет всю структуру. Вот рекурсивная функция flatten(), обрабатывающая произвольную глубину вложенности:
>>> def flatten(nested):
... flat = []
... for item in nested:
... if isinstance(item, list):
... flat.extend(flatten(item))
... else:
... flat.append(item)
... return flat
...
Внутри flatten() каждый элемент проверяется с помощью isinstance(). Если элемент сам является списком, функция рекурсивно выравнивает его и расширяет результат в flat. В противном случае элемент рассматривается как конечное значение и напрямую добавляется в flat
Вот как функция работает с нерегулярно вложенной структурой:
>>> nested = [1, [2, [3, [4, [5, 6]]]], 7, [8, 9]]
>>> flatten(nested)
[1, 2, 3, 4, 5, 6, 7, 8, 9]
Функция обходит всё дерево и возвращает единый плоский список. Важный момент: глубоко вложенные данные могут вызвать RecursionError, если стек вызовов превысит лимит рекурсии Python. Рекурсия также работает медленнее и сложнее в отладке — поэтому она не всегда лучший выбор для выравнивания данных
Использование цикла и стека
Если данные имеют неизвестную или очень глубокую вложенность и вы не хотите упираться в лимиты рекурсии, стоит переписать flatten() с использованием цикла и стека итераторов. Этот подход делает ту же работу, что и рекурсия, но полностью обходит связанные с ней ограничения:
>>> def flatten(nested):
... flat = []
... stack = [iter(nested)]
... while stack:
... for item in stack[-1]:
... if isinstance(item, list):
... stack.append(iter(item))
... break
... flat.append(item)
... else:
... stack.pop()
... return flat
...
>>> flatten([1, [2, [3, [4, [5, 6]]]], 7, [8, 9]])
[1, 2, 3, 4, 5, 6, 7, 8, 9]
В этой версии стек stack хранит по одному итератору на каждый уровень вложенности. На каждом проходе цикла while внутренний цикл for извлекает элементы из верхнего итератора. Плоские значения сразу попадают в flat. Вложенные списки помещают новый итератор на стек и выполняют break, чтобы следующий проход спустился в них. Когда итератор исчерпан, ветка else извлекает его из стека, и цикл возобновляется на внешнем уровне
Эта реализация полностью обходит лимит рекурсии и может обрабатывать структуры настолько глубокие, насколько позволяет доступная память — именно этот вариант я предпочитаю использовать, когда структура входных данных заранее неизвестна
Оценка производительности при выравнивании списков
К этому моменту мы разобрали несколько инструментов и техник для выравнивания списка списков в Python. Эффективность по времени выполнения — не абстрактная метрика, а реальный критерий выбора подхода, особенно в Python, который уступает по скорости C++ и Java
В этом разделе вы напишете небольшой скрипт, чтобы сравнить скорость разных подходов. Для начала создайте файл flatten.py и поместите в него все функции выравнивания, написанные ранее:
from functools import reduce
from itertools import chain
from operator import add, concat, iconcat
def flatten_extend(matrix): flat_list = [] for row in matrix: flat_list.extend(row) return flat_list
def flatten_concatenation(matrix): flat_list = [] for row in matrix: flat_list += row return flat_list
def flatten_comprehension(matrix): return [item for row in matrix for item in row]
def flatten_chain(matrix): return list(chain.from_iterable(matrix))
def flatten_reduce_lambda(matrix): return list(reduce(lambda x, y: x + y, matrix, []))
def flatten_reduce_add(matrix): return reduce(add, matrix, [])
def flatten_reduce_concat(matrix): return reduce(concat, matrix, [])
def flatten_reduce_iconcat(matrix): return reduce(iconcat, matrix, [])
def flatten_sum(matrix): return sum(matrix, [])
Обратите внимание: здесь проверяются все варианты использования reduce() — с add(), concat() и iconcat()
Затем создайте отдельный файл с кодом замера производительности:
from timeit import timeit
import flatten
SIZE = 1000
TO_MS = 1000
NUM = 10
FUNCTIONS = [ "flatten_extend", "flatten_concatenation", "flatten_comprehension", "flatten_chain", "flatten_reduce_lambda", "flatten_reduce_add", "flatten_reduce_concat", "flatten_reduce_iconcat", "flatten_sum",
]
matrix = [list(range(SIZE))] * SIZE
results = { func: timeit( f"flatten.{func}(matrix)", globals=globals(), number=NUM ) for func in FUNCTIONS
}
print(f"Time to flatten a {SIZE}x{SIZE} matrix (in milliseconds):\n")
for func, time in sorted(results.items(), key=lambda result: result[1]): print(f"{func + '()':.<30}{time * TO_MS / NUM:.>7.2f} ms")
Скрипт импортирует модуль flatten.py, задаёт четыре константы и список всех функций для сравнения, затем формирует тестовую матрицу matrix. Функция timeit() из одноимённого модуля измеряет время выполнения каждой функции, а словарное включение (dictionary comprehension) строит словарь с результатами. Цикл for выводит итоги, отсортированные по скорости
Запустите скрипт из командной строки — перед выводом потребуется некоторое время:
$ python performance.py
Time to flatten a 1000x1000 matrix (in milliseconds):
flatten_concatenation()..........5.29 ms
flatten_reduce_iconcat().........5.78 ms
flatten_extend().................6.62 ms
flatten_chain().................15.63 ms
flatten_comprehension()..........23.69 ms
flatten_reduce_lambda().......2645.11 ms
flatten_reduce_concat().......2652.81 ms
flatten_reduce_add()..........2658.96 ms
flatten_sum().................2684.48 ms
Разрыв между лидерами и аутсайдерами — колоссальный. При нескольких запусках скрипта flatten_concatenation(), flatten_extend() и flatten_reduce_iconcat() стабильно борются за первое место: все три мутируют существующий объект списка на месте, не создавая промежуточных копий
flatten_chain() держится на четвёртом месте, но работает примерно в три раза медленнее лидеров. Сама по себе chain() здесь не виновата — замедление вносит вызов list(), который материализует итератор в список. Если приоритет — экономия памяти, а не максимальная скорость, chain() остаётся отличным выбором
flatten_comprehension() стабильно занимает пятое место: списковое включение (list comprehension) вынуждено выполнять два вложенных цикла, что и сказывается на скорости
Функции на основе reduce() и flatten_sum() замыкают список с заметным отставанием. Причина понятна: каждый шаг создаёт новый промежуточный список, тогда как три лидера работают с единственным списком и изменяют его на месте — это одновременно быстрее и экономнее по памяти
Практический вывод: для выравнивания списка списков в Python оптимальный выбор — цикл for с оператором расширенной конкатенации (+=) или метод .extend(). flatten_sum() и большинство вариантов reduce() создают цепочку промежуточных списков, что делает их значительно медленнее остальных
Выравнивание списков для науки о данных с помощью NumPy
Многие специалисты по данным используют Python как основной язык. Если вы один из них, значительная часть работы — подготовка данных к обработке и анализу, и умение выравнивать списки списков там встречается регулярно
Плоский набор данных нужен, когда вы обучаете модель машинного обучения или запускаете алгоритм анализа данных
Предположим, у вас есть массив NumPy из вложенных массивов, представляющих матрицу, и вы хотите получить плоский массив со всеми данными. Массивы NumPy предоставляют метод .flatten(), который делает именно это:
>>> import numpy as np
>>> matrix = np.array(
... [
... [9, 3, 8, 3],
... [4, 5, 2, 8],
... [6, 4, 3, 1],
... [1, 0, 4, 5],
... ]
... )
>>> matrix
array([[9, 3, 8, 3], [4, 5, 2, 8], [6, 4, 3, 1], [1, 0, 4, 5]])
>>> matrix.flatten()
array([9, 3, 8, 3, 4, 5, 2, 8, 6, 4, 3, 1, 1, 0, 4, 5])
Вызов .flatten() на объекте matrix возвращает одномерный массив со всеми данными — многомерная структура преобразована в плоскую без лишних усилий
В этом руководстве вы разобрали несколько способов выравнивания списка списков в Python. Сначала — цикл
forс методом.extend(), затем — списковые включения,functools.reduce(),itertools.chain()иsum()Отдельно рассмотрели выравнивание на произвольной глубине: рекурсивный подход и итеративную версию с явным стеком для структур, превышающих лимит рекурсии Python
Тест производительности показал конкретный результат: лидируют цикл с
.extend()и оператор+=. Функции на основеreduce()иsum()создают промежуточные списки на каждом шаге и проигрывают по скорости в сотни раз на больших данныхВ финале — инструменты для науки о данных: метод
.flatten()из NumPy закрывает задачу выравнивания многомерных массивов одной строкой
Ответы на эти вопросы могут быть для вас полезными
Какой способ выравнивания списка списков самый быстрый в Python?
По результатам замеров производительности лидируют три подхода: цикл for с оператором +=, цикл for с методом .extend() и reduce() с функцией iconcat(). Все три мутируют единственный список на месте и не создают промежуточных копий
Когда стоит выбирать itertools.chain() вместо цикла с .extend()?
Когда приоритет — экономия памяти, а не максимальная скорость. chain.from_iterable() возвращает итератор и не материализует весь результат сразу, что важно при работе с большими объёмами данных
Почему sum() и reduce() с конкатенацией работают так медленно?
Каждый шаг создаёт новый промежуточный список: при матрице 1000×1000 это тысяча лишних аллокаций. Лидирующие функции обходятся одним списком на весь процесс, поэтому выигрывают в скорости в сотни раз
Как выровнять произвольно вложенные списки, если глубина вложенности неизвестна?
Используйте рекурсивную функцию, которая проверяет тип каждого элемента и вызывает себя для подсписков. Если структура очень глубокая и грозит превысить лимит рекурсии Python, замените рекурсию итеративной версией с явным стеком
Чем метод .flatten() NumPy отличается от встроенных способов Python?
.flatten() работает с объектами ndarray и возвращает одномерный массив NumPy, а не список Python. Это удобно в задачах машинного обучения и анализа данных, где данные уже хранятся в формате NumPy и нужно передать их в модель или алгоритм без дополнительных преобразований



