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 не решает проблему глубины рекурсии, но предотвращает повторные вычисления.

Python как функциональный язык - comments

En
Python функциональный язык (python)