itertools и functools
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
Функция group_by_first_letter(words) должна вернуть словарь «первая буква → список слов»
с помощью itertools.groupby. Слова в каждой группе — в алфавитном порядке, буквы — тоже.
Какие тесты проверяют код (2)
assert group_by_first_letter(["банан", "арбуз", "бобы", "абрикос"]) == {"а": ["абрикос", "арбуз"], "б": ["банан", "бобы"]}
assert group_by_first_letter([]) == {}
from itertools import groupby
def group_by_first_letter(words):
ordered = sorted(words)
return {letter: list(group) for letter, group in groupby(ordered, key=lambda w: w[0])}
Используя 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
from functools import partial, reduce
import operator
def power(base, exp):
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
def product(nums):
return reduce(operator.mul, nums, 1)
Напишите 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) == []
from itertools import combinations
def all_pairs_sum(nums, target):
return [(i, j) for i, j in combinations(range(len(nums)), 2) if nums[i] + nums[j] == target]
Что выведет код?
from itertools import groupby
print([k for k, _ in groupby("aabbaac")])
Сколько элементов выдаст выражение (для s = "abc")?
| 3 | 6 | 9 | 27 | |
|---|---|---|---|---|
permutations(s) |
||||
combinations(s, 2) |
||||
product(s, repeat=2) |
||||
product(s, repeat=3) |
||||
combinations_with_replacement(s, 2) |
Войдите, чтобы отмечать пройденные уроки.