Python как инструмент функционального программирования
Функциональное программирование в Python: обзор техник и примеров
Как обработать коллекцию без изменения исходных данных и без циклов?
Основной функциональный инструмент в Python - использование функций map, filter и reduce вместе с анонимными функциями lambda. Этот подход позволяет преобразовывать данные, не создавая побочных эффектов.
# Пример: удвоить каждый элемент списка
numbers = [1, 2, 3, 4]
doubled = list(map(lambda x: x * 2, numbers))
# Результат: [2, 4, 6, 8]
Python объектно ориентированный язык (объектно-ориентированное программирование в python)
[2, 4, 6, 8]
объектно ориентированный язык программирования python (python как объектно-ориентированный язык)
Функция map применяет лямбда-функцию к каждому элементу. filter оставляет только элементы, удовлетворяющие условию:
evens = list(filter(lambda x: x % 2 == 0, numbers))
# Результат: [2, 4]
Python функциональный язык (python как функциональный язык)
[2, 4]
reduce из модуля functools свёртывает коллекцию к одному значению:
from functools import reduce
sum_all = reduce(lambda a, b: a + b, numbers)
# Результат: 10
10
Типичная ошибка: забыть обернуть результат map/filter в list(), тогда возвращается итератор, который не содержит значений до перебора. В примерах выше это исправлено явным приведением.
Проблема производительности: лямбда-функции создаются заново при каждом вызове, что может замедлить выполнение внутри больших циклов. Для однократного использования это несущественно.
Цель и случаи использования: когда требуется применить одну и ту же операцию ко всем элементам коллекции, отфильтровать данные или вычислить свёртку. Метод особенно удобен при работе с потоками данных в сочетании с итераторами.
Как заменить map/filter на списковое включение для улучшения читаемости?
Списковые включения (list comprehensions) являются более pythonic-альтернативой для многих случаев использования map и filter. Они выполняются быстрее, так как не требуют вызова внешней функции.
numbers = [1, 2, 3, 4]
doubled = [x * 2 for x in numbers]
evens = [x for x in numbers if x % 2 == 0]
# Результат: [2,4,6,8] и [2,4]
Когда не подходит: списковые включения создают сразу весь список в памяти, что может быть неэффективно для огромных коллекций. В таких случаях лучше использовать генераторные выражения (круглые скобки).
Цель: повысить читаемость и скорость выполнения для небольших и средних объёмов данных.
Как частично применить функцию, фиксируя часть аргументов?
Модуль functools предоставляет функцию partial, которая создаёт новую функцию с заранее заданными аргументами. Это реализует концепцию каррирования.
from functools import partial
def power(base, exponent):
return base ** exponent
square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5)) # 25
print(cube(5)) # 125
25 125
Типичная ошибка: использование именованных аргументов при создании partial, если исходная функция ожидает позиционные аргументы. Лучше передавать аргументы как позиционные, если порядок фиксирован.
Цель: создание специализированных версий функций на лету, особенно полезно при передаче функции как аргумента в другие функции (например, в map).
Как реализовать рекурсию для функционального стиля и в чём её ограничения?
Рекурсия - основа функционального программирования. В Python можно писать рекурсивные функции, однако глубокая рекурсия может вызвать переполнение стека (максимум ~1000 вызовов).
# Факториал рекурсивно
def factorial(n):
if n == 0:
return 1
return n * factorial(n - 1)
print(factorial(5)) # 120
120
Ошибка RecursionError: при большом n (например, factorial(1000)). Решение - использовать итеративный подход или декоратор lru_cache для запоминания результатов, но это не решает проблему глубины стека.
Цель и случаи: рекурсия удобна для задач с естественным рекурсивным разбиением (обход деревьев, вычисление чисел Фибоначчи). Для больших данных в Python предпочтительнее итеративные методы.
Как генерировать данные лениво, не занимая память?
Генераторные выражения и функции-генераторы (с yield) позволяют работать с потенциально бесконечными последовательностями, вычисляя элементы по мере необходимости.
# Генератор квадратов натуральных чисел
squares = (x*x for x in range(10))
# Или функция-генератор
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
# Берём первые 10 чисел Фибоначчи
from itertools import islice
first_10 = list(islice(fib(), 10))
print(first_10)
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
Распространённая ошибка: попытка повторно использовать генератор после его полного исчерпания. Генератор можно пройти только один раз.
Цель: обработка больших потоков данных (логов, файлов, бесконечных последовательностей) без загрузки всего в память.
Как комбинировать итераторы для сложной обработки данных?
Модуль itertools содержит множество функций для комбинаторики и работы с итераторами: chain, groupby, product и другие. Они позволяют строить функциональные цепочки без дополнительных циклов.
from itertools import chain, groupby
# Слияние списков
list1 = [1, 2]
list2 = [3, 4]
merged = list(chain(list1, list2))
# Результат: [1, 2, 3, 4]
# Группировка по чётности
data = [1, 2, 3, 4, 5, 6]
grouped = groupby(data, key=lambda x: x % 2)
for key, group in grouped:
print(key, list(group))
1 [1, 3, 5] 0 [2, 4, 6]
Важно: groupby работает только на Сортированных последовательностях, иначе группировка будет некорректной. Необходимо предварительно отсортировать данные.
Цель: элегантная обработка коллекций, особенно когда требуется объединение, разбиение или перестановки элементов.
Расширенные примеры функционального программирования в Python
Композиция функций с помощью compose вручную
Создадим функцию, которая объединяет несколько функций в одну, передавая результат каждой следующей.
# Определяем композицию
def compose(*funcs):
def inner(arg):
result = arg
for f in reversed(funcs):
result = f(result)
return result
return inner
# Функции
def double(x): return x * 2
def increment(x): return x + 1
# Композиция: сначала increment, потом double
double_of_increment = compose(double, increment)
print(double_of_increment(5)) # (5+1)*2 = 12
12
Каррирование с использованием partial и lambda
Каррирование превращает функцию от нескольких аргументов в последовательность функций от одного аргумента.
# Каррированная функция sum
def curried_sum(a):
def inner(b):
return a + b
return inner
add_5 = curried_sum(5)
print(add_5(3)) # 8
# Более компактно с partial
from functools import partial
def add(a, b): return a + b
add_10 = partial(add, 10)
print(add_10(7)) # 17
8 17
Функция-декоратор как инструмент высшего порядка
Декоратор оборачивает функцию, добавляя побочное поведение (логирование, замер времени).
import time
def timer(func):
def wrapper(*args, **kwargs):
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} заняла {elapsed:.3f} сек")
return result
return wrapper
@timer
def slow_long(n):
return sum(range(n))
print(slow_long(10_000_000))
slow_long заняла 0.345 сек 49999995000000
Использование operator для замены лямбда
Модуль operator предоставляет готовые функции для арифметики и сравнений, что повышает производительность.
from operator import add, mul, gt
numbers = [1, 2, 3, 4, 5]
# Сумма всех элементов
total = reduce(add, numbers)
# Произведение всех элементов
product = reduce(mul, numbers)
# Фильтр чисел > 2
filtered = list(filter(lambda x: gt(x, 2), numbers))
print(total, product, filtered)
15 120 [3, 4, 5]
Ленивая фильтрация с itertools.filterfalse
Функция filterfalse возвращает элементы, для которых предикат даёт False.
from itertools import filterfalse
data = [1, 2, 3, 4, 5, 6]
# Числа, не являющиеся нечётными (т.е. чётные)
evens = list(filterfalse(lambda x: x % 2 == 1, data))
print(evens)
[2, 4, 6]
Транзакция данных через цепочку map и filter (без списковых включений)
Совмещение нескольких преобразований в одной цепочке.
numbers = range(1, 11)
# Взять квадраты чётных чисел
result = list(map(lambda x: x**2,
filter(lambda x: x % 2 == 0, numbers)))
print(result)
[4, 16, 36, 64, 100]
Создание бесконечного потока с itertools.count и islice
Пример генерации бесконечной последовательности и взятия её среза.
from itertools import count, islice
# Бесконечный счётчик, начиная с 10, шаг 3
counter = count(10, 3)
first_five = list(islice(counter, 5))
print(first_five)
[10, 13, 16, 19, 22]
Мемоизация с lru_cache для рекурсивных функций
Декоратор lru_cache автоматически запоминает результаты вызовов, ускоряя рекурсию.
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
print(fib(100))
354224848179261915075
Примечание: lru_cache не решает проблему глубины рекурсии, но предотвращает повторные вычисления.