map, filter, reduce, functools, itertools — functional tools
Python — не Haskell, но functional toolkit богат: map / filter — lazy iterators; functools.reduce — fold-аналог; functools.partial — currying; functools.lru_cache — single-line memoization; itertools — целый zoo lazy combinators (chain, islice, groupby, takewhile, и десятки других).
Этот урок покрывает требование PYTH-10 целиком. Все эти инструменты — про lazy evaluation (как gen-expr из урока 3) и composition (комбинирование маленьких функций в pipeline).
map / filter — lazy iterators
map(f, iter) возвращает итератор, yielding f(x) для каждого x из iter. Это не list — это lazy:
nums = [1, 2, 3, 4, 5]
doubled = map(lambda x: x * 2, nums)
print(type(doubled)) # <class 'map'> — iterator, не list
print(list(doubled)) # [2, 4, 6, 8, 10] — list() forces materialization
filter(pred, iter) аналогично — yields x где pred(x) truthy:
evens = filter(lambda x: x % 2 == 0, nums)
print(list(evens)) # [2, 4]
Часто эквивалентно gen-expression — выбор по читаемости:
# С map / filter:
doubled = map(lambda x: x * 2, nums)
evens = filter(lambda x: x % 2 == 0, nums)
# С gen-expr (обычно более Pythonic):
doubled = (x * 2 for x in nums)
evens = (x for x in nums if x % 2 == 0)
Pythonic preference: gen-expression для simple cases с lambda; map(named_func, iter) когда передаёте уже-named function — нет визуального шума lambda x: ради single call.
functools.reduce — fold
reduce(f, iter, initial=...) агрегирует iterator left-to-right с binary function. Эквивалент foldl в Haskell:
from functools import reduce
from operator import add, mul
reduce(add, [1, 2, 3, 4]) # 1+2+3+4 = 10
reduce(mul, [1, 2, 3, 4]) # 1*2*3*4 = 24
reduce(add, [1, 2, 3, 4], 100) # 100+1+2+3+4 = 110 (initial = 100)
# Конкатенация строк через reduce:
reduce(lambda a, b: a + b, ['A', 'B', 'C']) # 'ABC'
Семантика: для reduce(f, [x1, x2, x3], init) результат = f(f(f(init, x1), x2), x3). Без init первый элемент iterator используется как стартовое значение (требует не-пустой iter).
Когда использовать reduce: для свёрток, не покрытых sum / max / min / any / all. Если стандартный агрегатор подходит — используйте его (быстрее, читаемее).
Guido van Rossum (создатель Python) на python-dev говорил, что reduce — самая «нечитаемая» функция, и удалил её из builtins в Python 3 (теперь только в functools). Для повседневных свёрток предпочитайте sum / max / min / list-comp + agg.
functools.partial — currying
Создаёт новый callable с pre-filled аргументами:
from functools import partial
from operator import mul
double = partial(mul, 2) # bind первый arg = 2
triple = partial(mul, 3)
print(double(5)) # 10
print(triple(5)) # 15
# Kwargs тоже:
log_info = partial(print, '[INFO]')
log_info('msg1', 'msg2') # [INFO] msg1 msg2
# Адаптация sort key:
data = [(1, 'a'), (3, 'c'), (2, 'b')]
sorted_by_str = sorted(data, key=partial(lambda d, idx: d[idx], idx=1))
Внутри partial — простая структура: хранит func, args, kwargs; при вызове конкатенирует pre-filled с переданными. См. Modules/_functoolsmodule.c функция partial_call.
Use case: adapter pattern — когда чужая функция ожидает callable с определённой signature, а у вас функция с лишними параметрами; partial фиксирует «лишние» и возвращает signature-совместимый callable.
functools.lru_cache — memoization decorator
Декоратор кэширующий результаты функции по аргументам. LRU eviction (Least Recently Used) при достижении maxsize:
from functools import lru_cache
@lru_cache(maxsize=128)
def fib(n):
if n < 2:
return n
return fib(n - 1) + fib(n - 2)
print(fib(50)) # 12586269025 — мгновенно благодаря cache
print(fib.cache_info()) # CacheInfo(hits=48, misses=51, maxsize=128, currsize=51)
Без lru_cache fib(50) экспоненциально медленный (2⁵⁰ recursive вызовов). С cache — линейный по n (каждое значение вычислено один раз).
Под капотом: cache implementation использует dict (см. M02 урок 03 — open addressing с perturbation). Каждый вызов:
- Вычисляет hash от args (требует hashable!).
dict.getlookup — O(1) avg.- На hit возвращает cached value; на miss — вызывает функцию + сохраняет.
- На переполнении (
currsize > maxsize) — evict LRU (через linked list поверх dict).
См. Modules/_functoolsmodule.c — lru_cache_wrapper — C-accelerated. Pure-Python fallback в Lib/functools.py.
Args ОБЯЗАНЫ быть hashable. Если передадите list / dict / set — TypeError (‘unhashable type’). Используйте tuple вместо list, frozenset вместо set. См. M02 урок 06 — hashable требует stable identity, что требует immutability.
@lru_cache(maxsize=None) — unbounded cache (никогда не evict). @cache (Python 3.9+) — alias для lru_cache(maxsize=None), более лаконично:
from functools import cache
@cache
def expensive(x):
...
itertools — комбинаторика iterators
Целая библиотека lazy combinators. Highlights:
| Function | Назначение | Пример |
|---|---|---|
chain(*iters) | concatenate итераторы | chain([1,2], [3,4]) → 1, 2, 3, 4 |
islice(iter, stop) | slice iterator | islice(count(), 5) → 0, 1, 2, 3, 4 |
count(start, step) | infinite arithmetic | count(10, 2) → 10, 12, 14, … |
cycle(iter) | infinite repeat | cycle('ABC') → A, B, C, A, B, … |
repeat(x, n) | n times | repeat(0, 3) → 0, 0, 0 |
takewhile(pred, iter) | take пока true | takewhile(lambda x: x<5, range(10)) → 0..4 |
dropwhile(pred, iter) | drop пока true, потом yield rest | opposite of takewhile |
groupby(iter, key) | group consecutive (требует sorted!) | см. ниже |
permutations(iter, r) | r-перестановки | permutations([1,2,3], 2) → 6 tuples |
combinations(iter, r) | C(n, r) | combinations([1,2,3], 2) → 3 tuples |
product(*iters) | cartesian product | product([1,2], [3,4]) → 4 pairs |
tee(iter, n) | split iter на n independent | careful: amortized O(1) только если все consumers идут синхронно |
Пример — fibonacci через generator + islice:
from itertools import islice, count, chain
def fibs():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Первые 10 чисел:
print(list(islice(fibs(), 10))) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
groupby — особенный: группирует consecutive одинаковые элементы (требует pre-sorted input!):
from itertools import groupby
data = sorted(['apple', 'banana', 'cherry', 'avocado'], key=len)
# отсортировано по длине: ['apple', 'cherry', 'banana', 'avocado']
for length, items in groupby(data, key=len):
print(length, list(items))
# 5 ['apple']
# 6 ['cherry', 'banana']
# 7 ['avocado']
Cite: Modules/itertoolsmodule.c — все iterators реализованы на C, использующих iterator protocol (tp_iter / tp_iternext). Это link к Phase 66 M05 (iterators / generators).
См. https://docs.python.org/3/library/itertools.html для полного списка.
operator module — callable operators
Модуль operator предоставляет callable versions всех арифметических / сравнительных операторов:
from operator import add, mul, lt, eq, itemgetter, attrgetter
reduce(add, [1, 2, 3, 4]) # эквивалентно reduce(lambda a,b: a+b, [1,2,3,4])
# Сортировка по полю:
data = [{'age': 30, 'name': 'A'}, {'age': 25, 'name': 'B'}]
sorted(data, key=itemgetter('age')) # [B, A]
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
people = [Person('Alice', 30), Person('Bob', 25)]
sorted(people, key=attrgetter('age')) # [Bob, Alice]
itemgetter('age') — эквивалент lambda d: d['age'], но быстрее (no Python frame overhead — implemented in C). Для high-frequency sort’ов / map’ов — заметная разница.
attrgetter('age') — эквивалент lambda x: x.age. Обычная convention для key= параметра в sorted / min / max / groupby.
Cross-course context
Трансформации Spark DataFrame: map/filter в распределённом миреКлючевые выводы
- map / filter — lazy iterators; gen-expression — обычно более Pythonic эквивалент. Используйте
map(named_func, iter)без lambda. - functools.reduce — fold-агрегатор для свёрток за рамками
sum/max/min. Не злоупотреблять — читаемость страдает. - functools.partial — bind аргументов, возвращает новый callable. Adapter / proxy pattern.
- functools.lru_cache (или @cache) — single-line memoization. Args обязаны быть hashable (cache использует dict). C-accelerated.
- itertools — целый zoo lazy combinators. chain / islice / count / cycle / groupby / takewhile — основные. Все на iterator protocol — preview Phase 66 M05.
- operator module — callable operators (add, mul, itemgetter, attrgetter) — быстрее lambda для высокочастотных операций.