Эффективное деление списков на сегменты в языке Python

Раздел: Основы Python -> Разделение списка

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

Основной способ: разбиение на части фиксированного размера с помощью list comprehension

Самый простой и производительный метод - использование генератора списка и срезов. Функция принимает исходный список и размер части.

def split_list(lst, chunk_size):
    return [lst[i:i+chunk_size] for i in range(0, len(lst), chunk_size)]

my_list = [1,2,3,4,5,6,7,8,9]
print(split_list(my_list, 3))

Split list python (разделение списка в python)

[[1, 2, 3], [4, 5, 6], [7, 8, 9]]

Пояснение

  • range(0, len(lst), chunk_size) генерирует начальные индексы каждого блока: 0, 3, 6.
  • Срез lst[i:i+chunk_size] берет до 3 элементов, начиная с i. Последний срез может быть короче, если длина не кратна chunk_size.

Возможные проблемы:

  • Если chunk_size равен 0, возникнет ошибка деления на ноль и пустой range. Необходима проверка.
  • При очень большом списке результат занимает много памяти (создается новый список списков). Для потоковой обработки лучше использовать генератор.

Цели использования: подготовка данных для пакетной обработки, разбиение на страницы, распределение задач.

Как разбить список на N примерно равных частей?

Для равномерного распределения элементов по заданному числу частей используется расчет базового размера и остатка.

def split_into_n_parts(lst, n):
    avg = len(lst) // n
    remainder = len(lst) % n
    result = []
    start = 0
    for i in range(n):
        end = start + avg + (1 if i < remainder else 0)
        result.append(lst[start:end])
        start = end
    return result

print(split_into_n_parts([1,2,3,4,5,6,7], 3))
[[1, 2, 3], [4, 5], [6, 7]]

Цель: разбить данные на группы для параллельной обработки, когда количество групп фиксировано.

Проблема: если n больше длины списка, некоторые части будут пустыми. Желательно добавить проверку.

Как использовать itertools.islice для создания генератора частей?

Для экономии памяти и работы с большими списками удобно применять итератор, который возвращает части по мере необходимости.

from itertools import islice

def chunked_iter(lst, chunk_size):
    it = iter(lst)
    return iter(lambda: list(islice(it, chunk_size)), [])

for chunk in chunked_iter(range(10), 3):
    print(chunk)
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]

Этот метод не создает промежуточный список всех частей, что уменьшает потребление памяти.

Сложность: использование lambda и двусмысленность с пустым списком как сигналом остановки. Альтернатива: функция-генератор с yield.

Как разбить список с помощью zip и итераторов?

Классический трюк - объединение нескольких итераторов в один с помощью zip, но с предварительным созданием копий итератора.

def chunked_zip(lst, chunk_size):
    return [list(t) for t in zip(*[iter(lst)]*chunk_size)]

print(chunked_zip([1,2,3,4,5,6,7], 3))
[[1, 2, 3], [4, 5, 6]]

Важно: последний неполный блок отбрасывается. Это может быть нежелательно.

Если требуется сохранить хвост, нужно модифицировать подход, например, использовать itertools.zip_longest.

Цель: краткая запись, когда отбрасывание хвоста приемлемо.

Как использовать numpy для разбиения списка на части?

Если в проекте уже используется библиотека NumPy, функция numpy.array_split обеспечивает равномерное разбиение.

import numpy as np

my_list = [1,2,3,4,5,6,7,8,9,10]
parts = np.array_split(my_list, 3)
print(parts)
[array([1, 2, 3, 4]), array([5, 6, 7]), array([8, 9, 10])]

Результат - массив массивов. Для преобразования в списки: [list(p) for p in parts].

Требуется установка numpy, тип данных может меняться, избыточно для простых задач.

Как разбить список с помощью библиотеки more_itertools?

Библиотека more_itertools содержит готовую функцию chunked, которая возвращает список частей.

from more_itertools import chunked

my_list = [1,2,3,4,5,6,7,8]
print(list(chunked(my_list, 3)))
[[1, 2, 3], [4, 5, 6], [7, 8]]

Также есть ichunked для ленивого вычисления.

Не является стандартной библиотекой, требуется установка.

Как разбить список с помощью простого цикла for?

Иногда требуется более гибкое управление, например, с дополнительной обработкой каждого блока.

def split_manual(lst, chunk_size):
    chunks = []
    for i in range(0, len(lst), chunk_size):
        chunk = lst[i:i+chunk_size]
        # дополнительная обработка
        chunks.append(chunk)
    return chunks

print(split_manual([1,2,3,4,5], 2))
[[1, 2], [3, 4], [5]]

Цель: ясность кода, возможность вставить логику между блоками.

Расширенные примеры разделения списков

Скользящее окно (разделение с перекрытием)

Пример
def sliding_window(lst, window_size, step=1):
    return [lst[i:i+window_size] for i in range(0, len(lst)-window_size+1, step)]

print(sliding_window([1,2,3,4,5], 3))
[[1, 2, 3], [2, 3, 4], [3, 4, 5]]

Цель: анализ временных рядов, поиск паттернов.

Разделение списка по условию

Пример
def split_by_condition(lst, condition):
    true_list = [x for x in lst if condition(x)]
    false_list = [x for x in lst if not condition(x)]
    return true_list, false_list

even, odd = split_by_condition([1,2,3,4,5], lambda x: x%2==0)
print(even, odd)
[2, 4] [1, 3, 5]

Цель: фильтрация и группировка по произвольному правилу.

Разделение по списку длин

Пример
def split_by_sizes(lst, sizes):
    result = []
    start = 0
    for size in sizes:
        result.append(lst[start:start+size])
        start += size
    return result

print(split_by_sizes([1,2,3,4,5,6,7], [2,3,1]))
[[1, 2], [3, 4, 5], [6, 7]]

Цель: структурирование данных по заранее известным размерам блоков.

Разделение по произвольным индексам

Пример
from itertools import accumulate

def split_at_indices(lst, indices):
    indices = [0] + sorted(indices) + [len(lst)]
    return [lst[i:j] for i, j in zip(indices, indices[1:])]

print(split_at_indices([10,20,30,40,50,60], [2,5]))
[[10, 20], [30, 40, 50], [60]]

Цель: разбиение по заданным точкам разреза (например, разделы документа).

Генератор для экономии памяти

Пример
def chunked_generator(lst, chunk_size):
    for i in range(0, len(lst), chunk_size):
        yield lst[i:i+chunk_size]

for ch in chunked_generator(range(10), 3):
    print(ch)
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]

Используется для обработки больших списков без загрузки всех частей в память одновременно.

Использование zip_longest для сохранения хвоста

Пример
from itertools import zip_longest

def chunked_zip_longest(lst, chunk_size, fillvalue=None):
    args = [iter(lst)] * chunk_size
    return [list(filter(lambda x: x is not fillvalue, t)) for t in zip_longest(*args, fillvalue=fillvalue)]

print(chunked_zip_longest([1,2,3,4,5,6,7], 3))
[[1, 2, 3], [4, 5, 6], [7]]

Цель: сохранить последний неполный блок при использовании трюка с zip.

Разделение списка в Python - comments

En
Split list python (python)