Itertools в Python: рецепты вместо десятков строк кода
Группировка, скользящее окно, разбивка на пачки и декартово произведение — задачи, которые обычно решают вложенными циклами, укладываются в один вызов из itertools. Разбираем рабочие приёмы для Python 3.12.

Вложенный цикл со счётчиком, ручной срез списка на пачки по 500 штук, накопление промежуточной суммы через временную переменную — всё это писал каждый. Модуль itertools из стандартной библиотеки закрывает эти задачи одним вызовом, работает лениво (не держит весь результат в памяти) и почти не имеет накладных расходов, потому что реализован на C. Ниже — приёмы для повседневного кода на Python 3.12, где каждый заменяет 5–15 строк ручного цикла.
Речь именно о том, какой инструмент брать под конкретную задачу и чем он платит за удобство. Разбор устройства итераторов и генераторных выражений — отдельная тема, здесь только применение.
Что решаем и чем платим
Все функции itertools возвращают итераторы, а не списки. Это значит две вещи. Первая — они не занимают память под весь результат сразу, поэтому годятся для потоков и больших файлов. Вторая — итератор одноразовый: пройти по нему повторно нельзя, второй цикл получит пустоту. Если результат нужен дважды, оберните в list().
Ленивость — это не бесплатное ускорение. Вы выигрываете память и раннее прекращение обхода, но теряете возможность пройти по данным второй раз и узнать длину без полного прохода.
Группировка подряд идущих: groupby
Классическая ошибка: groupby группирует только соседние элементы, а не все одинаковые по коллекции. Если данные не отсортированы по ключу группировки — сначала сортируйте тем же ключом, иначе получите разорванные группы.
from itertools import groupby
from operator import itemgetter
# Python 3.12
zakazy = [
{"gorod": "Москва", "summa": 1200},
{"gorod": "Казань", "summa": 800},
{"gorod": "Москва", "summa": 3400},
{"gorod": "Казань", "summa": 150},
]
klyuch = itemgetter("gorod")
# сортировка тем же ключом обязательна
zakazy.sort(key=klyuch)
for gorod, gruppa in groupby(zakazy, key=klyuch):
itogo = sum(z["summa"] for z in gruppa)
print(gorod, itogo)
# Казань 950
# Москва 4600
Ещё одна ловушка: gruppa — это итератор, живущий только внутри текущей итерации внешнего цикла. Если сохранить его в список групп «на потом», к следующему шагу он окажется исчерпан. Материализуйте сразу: list(gruppa).
Скользящее окно и пары соседей: pairwise
С Python 3.10 в модуле есть pairwise — он выдаёт соседние пары. Задачи вроде «посчитать разницу между соседними замерами» или «найти, где ряд убывает» решаются без индексов.
from itertools import pairwise
zamery = [10, 12, 9, 20, 20, 5]
razniza = [b - a for a, b in pairwise(zamery)]
print(razniza) # [2, -3, 11, 0, -15]
Окно шире двух в стандартной библиотеке из коробки не выдаётся. Для окна произвольной длины на 3.12 берите itertools.batched (см. ниже) для непересекающихся кусков или коллекцию collections.deque с maxlen для скользящего.
Разбивка на пачки: batched
Появилась в Python 3.12. До неё пачки нарезали срезами в ручном цикле с шагом. Типичный сценарий — отправлять в API не по одному объекту, а группами по N.
from itertools import batched
idshniki = range(1, 11)
for pachka in batched(idshniki, 3):
print(pachka)
# (1, 2, 3)
# (4, 5, 6)
# (7, 8, 9)
# (10,) <- последняя пачка короче
Важно: последняя пачка может быть неполной, и это нормально. Не рассчитывайте, что все пачки одинаковой длины. Если код после этого делает pachka[2], на хвосте получите IndexError.
Если ваша версия ниже 3.12, batched недоступен — тогда используйте рецепт из документации на базе iter и islice, он приведён в разделе Itertools Recipes официальной документации.
Накопление: accumulate
Нарастающий итог, максимум-на-текущий-момент, факториал ряда — всё это accumulate. По умолчанию складывает, но принимает любую функцию двух аргументов.
from itertools import accumulate
import operator
prodazhi = [100, 50, 200, 30]
# нарастающий итог
print(list(accumulate(prodazhi))) # [100, 150, 350, 380]
# бегущий максимум
print(list(accumulate(prodazhi, max))) # [100, 100, 200, 200]
# произведение
print(list(accumulate(prodazhi, operator.mul))) # [100, 5000, 1000000, 30000000]
Комбинации без вложенных циклов
Три функции закрывают почти все переборы. Разница между ними — частая путаница на собеседованиях и в багах.
| Функция | Что даёт | Порядок важен | Повтор элемента |
|---|---|---|---|
product | декартово произведение | да | да |
permutations | перестановки | да | нет |
combinations | сочетания | нет | нет |
from itertools import product, combinations
razmery = ["S", "M", "L"]
cveta = ["чёрный", "белый"]
# все варианты товара: замена двойного вложенного for
for r, c in product(razmery, cveta):
print(r, c)
# все пары складов для проверки маршрутов
sklady = ["A", "B", "C"]
print(list(combinations(sklady, 2)))
# [('A', 'B'), ('A', 'C'), ('B', 'C')]
Осторожно с размером: product([0,1], repeat=20) — это больше миллиона кортежей. Пока вы итерируете лениво — памяти не жрёт, но обернёте в list() — и получите гигабайты. Держите переборы ленивыми и прекращайте обход, как только нашли нужное.
Фильтрация и обрезка потока
Для потоковых данных полезны функции, которые режут по условию, а не по индексу:
takewhile(pred, it)— отдаёт элементы, пока условие истинно, и останавливается на первом ложном;dropwhile(pred, it)— пропускает начало, пока условие истинно, потом отдаёт всё подряд;filterfalse(pred, it)— обратныйfilter, оставляет то, что не прошло условие;islice(it, start, stop, step)— срез итератора без превращения его в список.
from itertools import islice
def beskonechnyy_schetchik():
n = 0
while True:
yield n
n += 1
# взять элементы с 10 по 15 из бесконечного источника
print(list(islice(beskonechnyy_schetchik(), 10, 15)))
# [10, 11, 12, 13, 14]
Когда что выбирать
- Нужны все сочетания полей формы или матрица вариантов товара —
product. - Считаете разницу или тренд между соседними точками —
pairwise. - Шлёте данные в API или БД партиями —
batched(3.12+) или рецепт наislice. - Нужен нарастающий итог или бегущий экстремум —
accumulate. - Группируете лог по подряд идущим одинаковым событиям —
groupbyпосле сортировки. - Данные не влезают в память или бесконечны — комбинируйте
islice,takewhileи держите всё ленивым.
Если результат нужен как список с длиной и повторным обходом, а данные заведомо небольшие — обычное списковое включение читается проще, и itertools не даст выигрыша. Инструмент оправдан на потоках, больших объёмах и там, где вложенный цикл превращается в лапшу.
Что делать дальше
Откройте раздел Itertools Recipes в документации itertools: там собраны готовые функции grouper, sliding_window, unique_everseen, которых нет в модуле напрямую, но которые построены из его кирпичей. Если рецепты используются часто, поставьте пакет more-itertools — он оформляет их как готовые функции. Перед применением проверяйте версию Python: batched требует 3.12, pairwise — 3.10.
Задачи: математика, физика, химия: Разбор задачи по шагам с проверкой и похожей задачей
Спросить за 10 ₽Источники: itertools — Functions creating iterators for efficient looping (Python docs)
Частые вопросы
Почему groupby разбивает одинаковые значения на несколько групп?
У меня Python 3.10, batched не импортируется — что делать?
Почему второй цикл по результату itertools ничего не выдаёт?
Опасно ли использовать product на больших входах?
itertools реально быстрее ручного цикла?
Как сделать скользящее окно шире двух элементов?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.