itertools и functools

⏱ 40 мин · 🧩 заданий: 5

itertools: «алгебра итераторов»

Все функции itertools ленивые и возвращают итераторы. Это быстрые (написаны на C) строительные блоки для конвейеров.

Бесконечные

from itertools import count, cycle, repeat, islice

print(list(islice(count(10, step=5), 4)))          # 10, 15, 20, 25, ...
print(list(islice(cycle("АБВ"), 7)))               # по кругу
print(list(repeat("x", 3)))
print(list(zip(count(1), ["a", "b", "c"])))         # нумерация, как enumerate(start=1)

Срезы и фильтры

from itertools import islice, takewhile, dropwhile, filterfalse, compress

data = [1, 3, 5, 8, 9, 11, 2]
print(list(islice(data, 2, 5)))                    # срез любого итератора
print(list(takewhile(lambda x: x < 8, data)))      # пока условие истинно
print(list(dropwhile(lambda x: x < 8, data)))      # пропустить, пока истинно
print(list(filterfalse(lambda x: x % 2, data)))    # противоположность filter
print(list(compress("ABCD", [1, 0, 1, 0])))        # выбор по маске

Склейка и нарезка

from itertools import chain, zip_longest, pairwise, batched

print(list(chain([1, 2], (3,), "ab")))                     # последовательно
print(list(chain.from_iterable([[1, 2], [3], []])))        # расплющить один уровень
print(list(zip_longest("abc", [1], fillvalue="-")))        # zip по самому длинному
print(list(pairwise([1, 4, 9, 16])))                       # соседние пары (3.10+)
print(list(batched(range(7), 3)))                          # куски по n (3.12+)

Накопление

from itertools import accumulate
import operator

sales = [100, 250, 80, 300]
print(list(accumulate(sales)))                       # нарастающий итог
print(list(accumulate(sales, max)))                  # текущий максимум
print(list(accumulate(sales, operator.mul, initial=1)))

groupby

Группирует соседние элементы с одинаковым ключом. Поэтому данные почти всегда нужно сначала отсортировать по тому же ключу:

from itertools import groupby

orders = [("Анна", 500), ("Боря", 200), ("Анна", 300), ("Боря", 100), ("Вера", 50)]
print([k for k, _ in groupby(orders, key=lambda o: o[0])])   # без сортировки — дубли групп

orders.sort(key=lambda o: o[0])
for name, group in groupby(orders, key=lambda o: o[0]):
    print(name, sum(amount for _, amount in group))

# классическая задача: сжатие строки RLE
s = "aaabccdddd"
print("".join(f"{ch}{len(list(g))}" for ch, g in groupby(s)))

Группа — итератор

Каждая group — итератор, который становится недействительным после перехода к следующей группе. Если нужно сохранить, сразу превращайте в список: list(group).

Комбинаторика

from itertools import product, permutations, combinations, combinations_with_replacement

print(list(product("ab", [1, 2])))                  # декартово произведение = вложенные for
print(len(list(product(range(10), repeat=4))))      # все PIN-коды из 4 цифр
print(list(permutations("abc", 2)))                 # размещения: порядок важен
print(list(combinations("abcd", 2)))                # сочетания: порядок не важен
print(list(combinations_with_replacement("ab", 2)))
Функция Порядок важен Повторы Количество для n=4, k=2
product(s, repeat=k) да да 16
permutations(s, k) да нет 12
combinations(s, k) нет нет 6
combinations_with_replacement(s, k) нет да 10

functools

partial — зафиксировать аргументы

from functools import partial

def send(to, subject, body, priority="normal"):
    return f"[{priority}] {to}: {subject} — {body}"

alert_admin = partial(send, "admin@x.ru", priority="high")
print(alert_admin("Диск", "Осталось 5%"))
print(alert_admin.func.__name__, alert_admin.args, alert_admin.keywords)

int_from_binary = partial(int, base=2)
print(int_from_binary("1011"))

reduce — свернуть последовательность

from functools import reduce
import operator

print(reduce(operator.add, [1, 2, 3, 4]))          # ((1 + 2) + 3) + 4
print(reduce(lambda acc, x: acc * 10 + x, [1, 2, 3], 0))   # цифры → число 123
nested = {"a": {"b": {"c": 42}}}
print(reduce(lambda d, key: d[key], ["a", "b", "c"], nested))   # доступ по пути

Для суммы, максимума, all/any есть встроенные функции — reduce нужен для нестандартных свёрток.

Кеширование: cache и lru_cache

from functools import lru_cache, cache

@lru_cache(maxsize=128)
def fib(n):
    return n if n < 2 else fib(n - 1) + fib(n - 2)

print(fib(100))
print(fib.cache_info())

@cache                                  # = lru_cache(maxsize=None), Python 3.9+
def slow_square(x):
    print(f"  вычисляю {x}²")
    return x * x

print(slow_square(4), slow_square(4))
slow_square.cache_clear()

Аргументы должны быть хешируемыми

Кеш — словарь от аргументов, поэтому fib([1, 2]) упадёт с TypeError: unhashable type: 'list'. И не вешайте lru_cache на методы экземпляров: кеш держит ссылку на self, и объекты не освобождаются.

wraps, total_ordering, cached_property

Уже знакомы по предыдущим урокам: @wraps сохраняет метаданные функции в декораторах, @total_ordering достраивает сравнения, @cached_property — ленивый атрибут с кешем.

singledispatch — перегрузка по типу

from functools import singledispatch
from datetime import date

@singledispatch
def to_text(value):
    return str(value)

@to_text.register
def _(value: list):
    return ", ".join(to_text(v) for v in value)

@to_text.register
def _(value: date):
    return value.strftime("%d.%m.%Y")

@to_text.register
def _(value: bool):
    return "да" if value else "нет"

print(to_text(42), "|", to_text(True), "|", to_text([date(2026, 1, 9), False, 3.5]))

operator: функции вместо лямбд

from operator import itemgetter, attrgetter, methodcaller, add, mul
from collections import namedtuple

rows = [("Анна", 28), ("Боря", 21), ("Вера", 35)]
print(sorted(rows, key=itemgetter(1)))             # вместо lambda r: r[1]
print(list(map(itemgetter(0), rows)))

User = namedtuple("User", "name age")
users = [User(*r) for r in rows]
print(max(users, key=attrgetter("age")).name)      # вместо lambda u: u.age

print(list(map(methodcaller("upper"), ["a", "b"])))
print(list(map(add, [1, 2], [10, 20])), list(map(mul, [2, 3], [4, 5])))

itemgetter(1, 0) возвращает кортеж — удобно для сортировки по нескольким полям.

Типичная задача

«Сгруппируйте анаграммы», «сожмите строку», «найдите все пары с суммой» — на собеседовании решаются через defaultdict, groupby, Counter и combinations. Знание этих инструментов показывает уровень владения языком, но будьте готовы реализовать то же вручную и назвать сложность.

Итог

  • itertools: бесконечные (count, cycle), срезы (islice, takewhile), склейка (chain, zip_longest, pairwise, batched), accumulate, groupby (по соседним — сортируйте!), комбинаторика.
  • functools: partial, reduce, cache/lru_cache, wraps, total_ordering, cached_property, singledispatch.
  • operator.itemgetter/attrgetter — быстрые и читаемые ключи сортировки.

Практика решено 0 из 5

Вы не вошли — ответы проверяются, но прогресс не сохранится. Войти или зарегистрироваться.
Задание 1 Программа на Python

Функция group_by_first_letter(words) должна вернуть словарь «первая буква → список слов» с помощью itertools.groupby. Слова в каждой группе — в алфавитном порядке, буквы — тоже.

Какие тесты проверяют код (2)
assert group_by_first_letter(["банан", "арбуз", "бобы", "абрикос"]) == {"а": ["абрикос", "арбуз"], "б": ["банан", "бобы"]}
assert group_by_first_letter([]) == {}
Задание 2 Программа на Python

Используя functools.partial, создайте из функции power(base, exp) две новые: square (возводит в квадрат) и cube (в куб). Затем с помощью functools.reduce напишите product(nums) — произведение элементов списка (для пустого списка — 1).

Какие тесты проверяют код (3)
assert square(7) == 49 and cube(2) == 8
from functools import partial; assert isinstance(square, partial), "square должен быть partial"
assert product([2, 3, 4]) == 24 and product([]) == 1
Задание 3 Программа на Python

Напишите all_pairs_sum(nums, target) — список всех пар индексов (i, j), i < j, у которых nums[i] + nums[j] == target. Используйте itertools.combinations.

Какие тесты проверяют код (3)
assert all_pairs_sum([1, 2, 3, 4, 5], 6) == [(0, 4), (1, 3)]
assert all_pairs_sum([3, 3, 3], 6) == [(0, 1), (0, 2), (1, 2)]
assert all_pairs_sum([1], 2) == []
Задание 4 Один вариант ответа

Что выведет код?

from itertools import groupby
print([k for k, _ in groupby("aabbaac")])
Задание 5 Заполнить таблицу

Сколько элементов выдаст выражение (для s = "abc")?

36927
permutations(s)
combinations(s, 2)
product(s, repeat=2)
product(s, repeat=3)
combinations_with_replacement(s, 2)
Отметьте ячейку — «+», пустая — «−».

Войдите, чтобы отмечать пройденные уроки.