Functools.reduce: примеры (PYTHON)

Использование функции reduce из модуля functools в Python
Раздел: Функциональное программирование, Агрегация
functools.reduce(function, iterable, initializer): object

Основы функции functools.reduce

Функция reduce() из модуля functools применяет переданную функцию function кумулятивно к элементам итерируемого объекта iterable, сводя его к единственному значению. Её основное применение связано с операциями накопления результата.

Функция используется, когда требуется последовательно обработать элементы коллекции и получить агрегированный результат, такой как сумма, произведение, поиск максимального значения или объединение структур данных.

Аргументы функции:

  • function - функция двух аргументов. Первый аргумент представляет текущее накопленное значение, второй - следующий элемент из iterable.
  • iterable - итерируемый объект, элементы которого подлежат обработке.
  • initializer (опциональный) - начальное значение для аккумулятора. Если значение указано, вычисления начинаются с него. При отсутствии initializer функция использует первый элемент iterable в качестве начального значения, и обработка начинается со второго элемента.

Возвращаемое значение - финальное накопленное значение. При пустом iterable и отсутствии initializer вызывается исключение TypeError. Если iterable пуст, но задан initializer, возвращается значение initializer.

Примеры использования functools.reduce

Вычисление суммы чисел:

from functools import reduce
result = reduce(lambda acc, x: acc + x, [1, 2, 3, 4])
print(result)
10

Вычисление произведения с начальным значением:

from functools import reduce
result = reduce(lambda acc, x: acc * x, [1, 2, 3, 4], 10)
print(result)
240

Поиск максимального элемента:

from functools import reduce
result = reduce(lambda a, b: a if a > b else b, [5, 1, 8, 3])
print(result)
8

Объединение списка строк:

from functools import reduce
result = reduce(lambda acc, s: acc + '-' + s, ['a', 'b', 'c'], 'start')
print(result)
start-a-b-c

Альтернативные функции в Python

В стандартной библиотеке Python существуют функции для частных случаев свертки:

  • sum(iterable, start=0) - специализированная функция для сложения чисел. Предпочтительнее reduce для суммирования из-за лучшей читаемости и производительности.
  • math.prod(iterable, start=1) - вычисляет произведение элементов. Появилась в Python 3.8 как специализированная альтернатива.
  • any(iterable) и all(iterable) - проверяют логические условия. Эффективнее и понятнее соответствующих конструкций с reduce.
  • itertools.accumulate(iterable, func=operator.add) - возвращает итератор по промежуточным результатам свертки, а не только по конечному значению.

Выбор между reduce и специализированной функцией зависит от ясности кода. Встроенные функции sum и prod являются предпочтительными для арифметических операций.

Аналоги функции в других языках

Идея операции свертки присутствует во многих языках программирования.

JavaScript (Array.prototype.reduce):

const result = [1, 2, 3, 4].reduce((acc, val) => acc + val, 0);
console.log(result);
10

PHP (array_reduce):

$result = array_reduce([1, 2, 3, 4], function($carry, $item) {
    return $carry + $item;
}, 0);
echo $result;
10

Kotlin (fold и reduce):

val list = listOf(1, 2, 3, 4)
val foldResult = list.fold(0) { acc, i -> acc + i }
val reduceResult = list.reduce { acc, i -> acc + i }
println("fold: $foldResult, reduce: $reduceResult")
fold: 10, reduce: 10

Отличия часто заключаются в наличии двух вариантов: с начальным значением (fold) и без него (reduce), а также в порядке аргументов передаваемой функции. В Python есть только одна функция reduce, которая через аргумент initializer реализует оба сценария.

Распространенные ошибки

Передача пустой последовательности без начального значения:

from functools import reduce
try:
    result = reduce(lambda x, y: x + y, [])
except TypeError as e:
    print(f"Ошибка: {e}")
Ошибка: reduce() of empty iterable with no initial value

Использование функции с неправильным числом аргументов:

from functools import reduce
try:
    # Функция ожидает два аргумента, а не один
    result = reduce(lambda x: x * 2, [1, 2, 3])
except TypeError as e:
    print(f"Ошибка: {e}")
Ошибка: <lambda>() takes 1 positional argument but 2 were given

Непреднамеренное изменение типа данных при работе с разными типами:

from functools import reduce
# Начальное значение 0 (int) приводит к преобразованию строки в операторе +
result = reduce(lambda acc, s: acc + len(s), ["a", "bb"], 0)
print(result)
# Но если забыть initializer, первая операция будет 'a' + len('bb'), что вызовет ошибку.
try:
    result = reduce(lambda acc, s: acc + len(s), ["a", "bb"])
except TypeError as e:
    print(f"Другая ошибка: {e}")
3
Другая ошибка: can only concatenate str (not "int") to str

Изменения в последних версиях

В Python 3.x функция functools.reduce не претерпела значительных изменений в своей основе. Первоначально она была встроенной функцией reduce в Python 2.x. В Python 3.0 её переместили в модуль functools в рамках общего стремления к тому, чтобы явно импортировать функции высшего порядка.

Интерфейс и поведение функции остаются стабильными. Все улучшения касаются в основном производительности и внутренней реализации, незаметной для пользователя.

Расширенные примеры применения

Построение вложенного словаря из последовательности пар ключ-значение:

Пример python
from functools import reduce
data = [('a', 1), ('b', 2), ('c', 3)]
result = reduce(lambda d, item: {**d, item[0]: item[1]}, data, {})
print(result)
{'a': 1, 'b': 2, 'c': 3}

Композиция нескольких функций:

Пример python
from functools import reduce
def compose(*funcs):
    return reduce(lambda f, g: lambda x: f(g(x)), funcs, lambda x: x)
def add_two(x):
    return x + 2
def mul_three(x):
    return x * 3
composed = compose(add_two, mul_three, add_two)
print(composed(5))  # ((5 + 2) * 3) + 2
23

Поиск самого длинного слова в списке:

Пример python
from functools import reduce
words = ['Python', 'reduce', 'function', 'example']
longest = reduce(lambda a, b: a if len(a) > len(b) else b, words)
print(longest)
function

Суммирование только четных чисел с накоплением промежуточных результатов:

Пример python
from functools import reduce
steps = []
def sum_even(acc, x):
    if x % 2 == 0:
        steps.append(f"Add {x} to {acc}")
        return acc + x
    steps.append(f"Skip {x}")
    return acc
result = reduce(sum_even, [1, 2, 4, 3, 6], 0)
print("Result:", result)
print("Steps:", ' -> '.join(steps))
Result: 12
Steps: Skip 1 -> Add 2 to 0 -> Add 4 to 2 -> Skip 3 -> Add 6 to 6

Рекурсивное объединение списков из вложенной структуры:

Пример python
from functools import reduce
nested = [[1, 2], [3, [4, 5]], [6]]
flatten = reduce(lambda acc, val: acc + (flatten(val) if isinstance(val, list) else [val]), nested, [])
def flatten(lst):
    return reduce(lambda acc, val: acc + (flatten(val) if isinstance(val, list) else [val]), lst, [])
print(flatten(nested))
[1, 2, 3, 4, 5, 6]

питон functools.reduce function comments

En
Functools.reduce Apply function cumulatively