Skip to content

Latest commit

 

History

History
149 lines (106 loc) · 7.37 KB

File metadata and controls

149 lines (106 loc) · 7.37 KB

Модуль itertools

← Comprehensions · 🏠 Домой · Модуль collections →


Что такое itertools и почему его стоит знать?

Коротко. Набор строительных блоков для итераторов, реализованных на C. Все они ленивые — возвращают итератор, а не список, — и заменяют типовые ручные циклы, которые иначе пишутся с ошибками.

Три группы:

Группа Функции
Бесконечные count, cycle, repeat
Конечные, над входом chain, islice, groupby, accumulate, zip_longest, tee, batched
Комбинаторные product, permutations, combinations

Подвох. Ленивость означает одноразовость: результат любой из этих функций проходится один раз, len() от него не берётся, а печать даёт <itertools.chain object at ...>, а не содержимое. Чтобы посмотреть — list().


Как склеить несколько последовательностей и взять из них срез?

Коротко. chain проходит несколько итерируемых подряд как одно, не создавая промежуточного списка. islice — срез для итератора, у которого обычный синтаксис it[a:b] не работает.

from itertools import chain, islice, count

list(chain([1, 2], (3,), "ab"))
# [1, 2, 3, 'a', 'b']

list(chain.from_iterable([[1, 2], [3]]))   # когда итерируемые в одном списке
# [1, 2, 3]

list(islice(count(10), 2, 6))              # срез бесконечного счётчика
# [12, 13, 14, 15]

chain выигрывает у list1 + list2 на больших данных: конкатенация копирует всё в новый список, chain не копирует ничего. А islice — штатный способ взять «первые N» из потока (см. Итератор, где отсутствие срезов у итераторов отмечено как ограничение).

Подвох. islice не умеет отрицательные индексы: islice(it, -3, None) даёт ValueError. Иначе пришлось бы дочитать итератор до конца — а он может быть бесконечным. «Последние N» берут через collections.deque(it, maxlen=N).


Почему groupby возвращает не то, что ожидают?

Коротко. groupby группирует только соседние элементы с одинаковым ключом. Он не сортирует вход — это осознанное решение, чтобы работать потоково и в постоянной памяти.

from itertools import groupby

data = [("a", 1), ("a", 2), ("b", 3), ("a", 4)]
key = lambda t: t[0]

[(k, [v for _, v in g]) for k, g in groupby(data, key=key)]
# [('a', [1, 2]), ('b', [3]), ('a', [4])]   — 'a' встретилось дважды

[(k, [v for _, v in g]) for k, g in groupby(sorted(data, key=key), key=key)]
# [('a', [1, 2, 4]), ('b', [3])]            — после сортировки как надо

Подвох. Вторая ловушка серьёзнее: группа — это итератор, живущий до перехода к следующей группе. Сохранить группы «на потом» нельзя:

saved = list(groupby([1, 1, 2]))
[(k, list(g)) for k, g in saved]
# [(1, []), (2, [])]   — группы уже исчерпаны

Материализовать группу нужно сразу — list(g) внутри цикла.

Глубже. Когда группировать надо по всей последовательности, а сортировка дорога, defaultdict(list) в один проход и дешевле, и понятнее (см. Модуль collections). groupby уместен там, где данные уже упорядочены: строки отсортированного файла, ответ базы с ORDER BY, поток событий по времени.


Что даёт комбинаторная часть?

Коротко. product — декартово произведение (вложенные циклы), permutations — размещения (порядок важен), combinations — сочетания (порядок не важен, только «вперёд» по входу).

from itertools import product, permutations, combinations

list(product([1, 2], "ab"))
# [(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b')]

list(permutations("abc", 2))
# [('a','b'), ('a','c'), ('b','a'), ('b','c'), ('c','a'), ('c','b')]

list(combinations("abc", 2))
# [('a','b'), ('a','c'), ('b','c')]

product(xs, repeat=3) заменяет три вложенных цикла по одному и тому же набору — типовой перебор в задачах на собеседовании.

Подвох. Уникальность элементов не проверяется: permutations работает по позициям, поэтому на входе "aab" появятся визуально одинаковые кортежи. И размер результата растёт факториально — материализовать его в список без ограничения по islice опасно.


Что ещё полезно назвать?

  • accumulate(iterable, func=operator.add) — running total ([1,2,3,4] → [1, 3, 6, 10]); с func=max даёт бегущий максимум.

  • zip_longest(*iterables, fillvalue=...) — zip останавливается по короткому входу и молча теряет хвост; zip_longest дополняет заполнителем. Третий вариант — zip(..., strict=True) (Python 3.10+), который на разной длине бросает ValueError.

  • batched(iterable, n) (Python 3.12+) — нарезка потока на пачки: то, что раньше писали руками для батчевых вставок в базу.

    # python 3.12+
    from itertools import batched
    list(batched("abcdefg", 3))
    # [('a','b','c'), ('d','e','f'), ('g',)]
  • tee(iterable, n) — «раздвоить» итератор. Помнить о цене: tee буферизует элементы, которые уже прочитала одна ветка и ещё не прочитала другая. Если ветки расходятся далеко, в памяти окажется весь вход, и проще сделать list().


← Comprehensions · 🏠 Домой · Модуль collections →