Функциональное программирование
map/filter, lru_cache, рекурсия и хвостовая рекурсия.
10 вопросов
JuniorТеорияОчень частоЧто такое функция высшего порядка?
Что такое функция высшего порядка?
Функция, которая принимает одну или несколько функций как аргументы и/или возвращает функцию. Это возможно, потому что в Python функции — объекты первого класса. Примеры: map, filter, sorted(key=...) и декораторы.
Типичные ошибки
- ✗Путать арность (число параметров) с тем, что функция высшего порядка
- ✗Думать, что рекурсия делает функцию функцией высшего порядка
- ✗Считать, что лишь builtins, а не функции через
def, бывают высшего порядка
Уточняющие вопросы
- →Как декоратор использует свойство высшего порядка, чтобы обернуть функцию?
- →Почему функцию можно передать в
sortedчерез параметрkey?
JuniorТеорияЧастоЧто делают map, filter и reduce?
Что делают map, filter и reduce?
map(f, seq) применяет f к каждому элементу; filter(pred, seq) оставляет элементы, где pred истинно, — оба возвращают ленивые итераторы в Python 3. reduce(f, seq[, init]) из functools сворачивает последовательность в одно значение.
Типичные ошибки
- ✗Ожидать, что
map/filterвернут списки, а не ленивые итераторы в Python 3 - ✗Вызывать
reduceбезfrom functools import reduceв Python 3 - ✗Думать, что
filterоставляет элементы, где предикат равен False
Уточняющие вопросы
- →Как генераторное выражение часто заменяет
mapиfilterвместе? - →Какую роль играет необязательный аргумент
initвreduce?
JuniorТеорияЧастоЧто такое функциональное программирование и как Python его поддерживает?
Что такое функциональное программирование и как Python его поддерживает?
Парадигма, трактующая вычисление как применение функций и предпочитающая чистые функции и неизменяемость изменяемому состоянию. Python поддерживает её частично: функции первого класса, lambda, comprehensions, functools.
Типичные ошибки
- ✗Называть Python чисто функциональным языком, тогда как он лишь поддерживает функциональные возможности
- ✗Сводить функциональный стиль только к
lambda, забывая про чистые функции и неизменяемость - ✗Забывать, что comprehensions и
functools/itertools— основные функциональные инструменты
Уточняющие вопросы
- →Что делает функцию чистой и почему чистота помогает тестированию и рассуждению?
- →Какие возможности Python уводят от чисто функционального стиля?
JuniorТеорияЧастоЧто такое рекурсия и какие два случая ей нужны?
Что такое рекурсия и какие два случая ей нужны?
Рекурсия — это функция, вызывающая саму себя. Ей нужен базовый случай (условие завершения без рекурсии) и рекурсивный случай (вызов себя на меньшем входе к базе). Без базы — бесконечная рекурсия.
Типичные ошибки
- ✗Пропускать или неверно задавать базовый случай, вызывая бесконечную рекурсию
- ✗Не уменьшать вход к базовому случаю при каждом вызове
- ✗Путать рекурсию с обычным циклом без правила завершения
Уточняющие вопросы
- →Какую ошибку выбрасывает CPython при слишком глубокой рекурсии?
- →Как переписать рекурсивную функцию в виде итеративного цикла?
MiddleТеорияЧастоЧто даёт модуль itertools и зачем его использовать?
Что даёт модуль itertools и зачем его использовать?
Ленивые, экономные по памяти строительные блоки для итераторов. count, cycle и repeat бесконечны; chain склеивает итерируемые; islice лениво режет без материализации; groupby, product и combinations дают группировку и комбинаторику. Они стримят вход, а не строят списки.
Типичные ошибки
- ✗Звать
list()на бесконечном итераторе вродеcount()илиcycle() - ✗Ждать, что
groupbyсгруппирует глобально без предварительной сортировки входа - ✗Думать, что результаты
itertools— переиспользуемые списки, а не одноразовые итераторы
Уточняющие вопросы
- →Почему вход нужно предварительно сортировать, чтобы
itertools.groupbyгруппировал как ожидается? - →Чем
isliceотличается от обычного срезаseq[a:b]на генераторе?
MiddleТеорияЧастоЧто делает functools.lru_cache?
Что делает functools.lru_cache?
Декоратор, выполняющий мемоизацию функции: он кэширует результаты по аргументам вызова и отдаёт сохранённый результат при повторах, вытесняя наименее недавно использованные записи сверх maxsize. Аргументы должны быть хэшируемыми.
Типичные ошибки
- ✗Кэшировать нечистые функции, результат которых зависит от побочных эффектов или внешнего состояния
- ✗Декорировать функцию с нехэшируемыми аргументами вроде списков или словарей
- ✗Забывать, что
maxsizeограничивает кэш и вытесняет старые записи
Уточняющие вопросы
- →Почему аргументы кэшируемой функции должны быть хэшируемыми?
- →Как
lru_cacheускоряет наивную рекурсивную функцию Фибоначчи?
MiddleТеорияИногдаЧто такое каррирование и как с ним связан functools.partial?
Что такое каррирование и как с ним связан functools.partial?
Каррирование превращает функцию многих аргументов в цепочку функций одного аргумента, каждая возвращает следующую. Частичное применение — functools.partial(f, a) — фиксирует часть аргументов и возвращает callable для остальных.
Типичные ошибки
- ✗Смешивать каррирование (по одному аргументу) с вызовом всех аргументов сразу
- ✗Считать, что
functools.partialменяет исходную функцию, а не оборачивает её - ✗Думать, что каррирование и частичное применение не связаны со специализацией функций
Уточняющие вопросы
- →Как реализовать каррирование вручную через вложенные функции или замыкания?
- →Когда
functools.partialпонятнее, чем написаниеlambda?
MiddleТеорияИногдаЧто даёт модуль operator и когда его используют?
Что даёт модуль operator и когда его используют?
Функциональные версии операторов и доступов Python — add, mul, lt, плюс itemgetter, attrgetter, methodcaller. Это более быстрые и picklable замены крошечных лямбд, в основном как key= в sorted/max или в functools.reduce, например sorted(rows, key=itemgetter(1)).
Типичные ошибки
- ✗Думать, что
operatorделает низкоуровневую/аппаратную работу, а не оборачивает операторы Python - ✗Путать его с перегрузкой операторов через dunder-методы
- ✗Считать, что
itemgetter/attrgetterмутируют, а не просто читают
Уточняющие вопросы
- →Почему
operator.itemgetter(1)предпочтительнееlambda r: r[1]как ключ сортировки? - →Почему
itemgetterможно сериализовать через pickle, а эквивалентную лямбду — нет?
MiddleТеорияИногдаЧто такое хвостовая рекурсия и как её писать?
Что такое хвостовая рекурсия и как её писать?
Хвостовая рекурсия — когда рекурсивный вызов является последним действием функции и после его возврата вычислять нечего; обычно достигается параметром-аккумулятором, несущим текущий результат, например fact(n-1, acc*n).
Типичные ошибки
- ✗Называть
return n * fact(n-1)хвостовым, несмотря на ожидающее умножение - ✗Путать позицию вызова в теле с тем, что он является последним действием
- ✗Считать, что аккумулятор устраняет необходимость в базовом случае
Уточняющие вопросы
- →Как аккумулятор превращает нехвостовой факториал в хвостово-рекурсивный?
- →Почему хвостовая рекурсия помогает только в языках, реализующих TCO?
SeniorТеорияРедкоОптимизирует ли CPython хвостовые вызовы?
Оптимизирует ли CPython хвостовые вызовы?
Нет. CPython не выполняет оптимизацию хвостовых вызовов — это намеренный выбор ради полных трейсбеков — поэтому даже хвостово-рекурсивные вызовы растят стек и упираются в лимит рекурсии (по умолчанию ~1000, sys.setrecursionlimit).
Типичные ошибки
- ✗Полагать, что CPython делает TCO как Scheme или некоторые функциональные языки
- ✗Думать, что
sys.setrecursionlimitвключает оптимизацию, а не просто поднимает лимит - ✗Считать, что хвостово-рекурсивный код защищён от
RecursionErrorв CPython
Уточняющие вопросы
- →Почему разработчики CPython отвергли TCO в пользу полных трейсбеков?
- →Как trampoline позволяет выполнять глубокую хвостовую рекурсию без роста стека?