Паттерны функционального программирования
Чистые функции, неизменяемость и композируемые конвейеры с functools и itertools
functools, itertools, включения и генераторы. При осознанном применении они дают код, который проще тестировать, кешировать и понимать — при бездумном применении получаются нечитаемые однострочники. Эта тема о применении ФП там, где оно окупается.
- дать определение чистой функции и объяснить ссылочную прозрачность,
- выбирать неизменяемые структуры (
tuple,frozenset, замороженныйdataclass) и избегать ошибок общего изменяемого состояния, - брать нужный инструмент из
functools:reduce,partial,lru_cache,singledispatch, - лениво обрабатывать поток данных с
itertools(chain,islice,groupby,accumulate,takewhile), - компоновать маленькие преобразования в читаемый конвейер,
- честно признавать, когда ФП вредит читаемости в Python.
1. Чистые функции и ссылочная прозрачность
Функция чистая, когда выполняет два правила: (1) её возвращаемое значение зависит только от аргументов и (2) она не производит наблюдаемых побочных эффектов — не меняет внешнее состояние, не делает ввод-вывод, не читает часы или случайные числа. Одни и те же входы всегда дают один и тот же результат.
# ЧИСТАЯ: результат зависит только от входов, ничего внешнего не меняется
def add_tax(price: float, rate: float) -> float:
return round(price * (1 + rate), 2)
# НЕЧИСТАЯ: читает глобальную, меняет глобальную и делает ввод-вывод
TOTAL = 0.0
def add_tax_bad(price: float) -> float:
global TOTAL
result = price * 1.2
TOTAL += result # побочный эффект: меняет внешнее состояние
print(result) # побочный эффект: ввод-вывод
return result
Ссылочная прозрачность означает, что любой вызов чистой функции можно заменить его результатом, не меняя программу. add_tax(100, 0.2) везде взаимозаменяем со значением 120.0. Именно это свойство делает чистые функции тривиально тестируемыми (без фикстур и моков), безопасными для кеширования и безопасными для параллельного запуска — нет общего состояния, которое можно испортить.