Функция reduce() в Python: от функционального к Pythonic стилю
Оглавление
- Изучение функционального программирования на Python
- Начало работы с Python'ом reduce()
- Сокращение повторяющихся значений с помощью функции reduce() в Python
- Сравнение reduce() и accumulate(накапливать)()
- С учетом производительности и удобочитаемости
- Заключение
В Python reduce() есть функция, которая реализует математический метод, называемый сворачивание или сокращение. reduce() полезно, когда вам нужно применить функцию к итерируемому объекту и свести его к одному суммарному значению. Язык Python reduce() популярен среди разработчиков, имеющих опыт функционального программирования, но Python может предложить больше.
В этом руководстве вы узнаете, как работает reduce() и как эффективно его использовать. Вы также познакомитесь с некоторыми альтернативными инструментами Python, которые могут быть более питоническими, удобочитаемыми и эффективными, чем reduce().
В этом уроке вы узнаете:
- Как работает Python
reduce() - Каковы наиболее распространенные сокращения вариантов использования
- Как решить эти варианты использования с помощью
reduce() - Какие альтернативные инструменты Python доступны для решения этих же задач
Обладая этими знаниями, вы сможете решить, какие инструменты использовать, когда дело дойдет до решения задач сокращения или сворачивания в Python.
Для лучшего понимания функций Python reduce() было бы полезно иметь некоторые предварительные знания о том, как работать с итерируемыми элементами Python, особенно о том, как перебирать их с помощью a for петля.
Скачать бесплатно: Ознакомьтесь с примером главы из книги "Приемы работы с Python: Книга", в которой показаны лучшие практики Python на простых примерах, которые вы можете подайте заявку немедленно, чтобы написать более красивый + Pythonic код.
Изучение функционального программирования на Python
Функциональное программирование - это парадигма программирования, основанная на разбиении задачи на набор отдельных функций. В идеале, каждая функция принимает только набор входных аргументов и выдает выходные данные.
В функциональном программировании функции не имеют какого-либо внутреннего состояния, влияющего на выходные данные, которые они выдают для данного входного сигнала. Это означает, что всякий раз, когда вы вызываете функцию с одним и тем же набором входных аргументов, вы получаете один и тот же результат или выходные данные.
В функциональной программе входные данные передаются через набор функций. Каждая функция обрабатывает свои входные данные и выдает некоторый результат. Функциональное программирование старается по возможности избегать изменчивых типов данных и изменений состояния. Оно работает с данными, которые передаются между функциями.
Другие основные возможности функционального программирования включают следующее:
- Использование рекурсии вместо циклов или других структур в качестве основной структуры управления потоком
- Основное внимание уделяется обработке списков или массивов
- Акцент на что должно быть вычислено, а не на как это вычислить
- Использование чистых функций, которые позволяют избежать побочных эффектов
- Использование Функций более высокого порядка
В этом списке есть несколько важных понятий. Вот более подробный обзор некоторых из них:
-
Рекурсия - это метод, при котором функции вызывают сами себя, прямо или косвенно, для выполнения цикла. Это позволяет программе выполнять цикл по структурам данных неизвестной или непредсказуемой длины.
-
Чистые функции - это функции, которые вообще не имеют побочных эффектов. Другими словами, это функции, которые не обновляют и не модифицируют какую-либо глобальную переменную, объект или структуру данных в программе. Эти функции выдают выходные данные, зависящие только от входных данных, что ближе к понятию математической функции.
-
Функции более высокого порядка - это функции, которые работают с другими функциями, принимая функции в качестве аргументов, возвращая функции или и то, и другое, как в случае с Декораторами Python.
Поскольку Python является многопарадигмальным языком программирования, он предоставляет некоторые инструменты, поддерживающие функциональный стиль программирования:
- Функционирует как первоклассные объекты
- Рекурсия возможности
- Анонимные функции с
lambda - Генераторы итераторов и
- Стандартные модули, такие как
functoolsиitertools - Такие инструменты, как
map(),filter(),reduce(),sum(),len(),any(),all(),min(),max(), и так далее
Несмотря на то, что функциональные языки программирования не оказали на Python сильного влияния, еще в 1993 году существовал явный спрос на некоторые из перечисленных выше функций функционального программирования.
В ответ на это в язык было добавлено несколько функциональных инструментов. Согласно Гвидо ван Россуму (Guido van Rossum), они были добавлены участником сообщества:
Python приобрел
lambda,reduce(),filter()иmap(), любезно предоставленные (я полагаю) хакером Lisp, который пропустил их и отправил рабочие исправления. (Источник)
На протяжении многих лет появлялись новые функции, такие как список значений, генераторные выражения и встроенные функции, такие как sum(), min(), max(), all(), и any() рассматривались как питонические замены map(), filter(), и reduce(). Guido планировал удалить map(), filter(), reduce(), и даже lambda из языка в Python 3.
К счастью, это удаление не вступило в силу, главным образом потому, что сообщество Python не хотело отказываться от таких популярных функций. Они все еще существуют и по-прежнему широко используются разработчиками с большим опытом функционального программирования.
В этом руководстве вы узнаете, как использовать reduce() в Python для обработки повторяющихся значений и приведения их к одному суммарному значению без использования цикла for. Вы также узнаете о некоторых инструментах Python, которые можно использовать вместо reduce(), чтобы сделать ваш код более понятным, читабельным и эффективным.
Начало работы с Python reduce()
В Python reduce() реализован математический метод, широко известный как сворачивание или сокращение. Вы сворачиваете или сокращаете список элементов, когда сводите его к одному суммарному значению. reduce() в Python работает с любыми повторяемыми — не только со списками — и выполняет следующие шаги:
- Примените функцию (или вызываемый элемент) к первым двум элементам в iterable и сгенерируйте частичный результат.
- Используйте этот частичный результат вместе с третьим элементом в iterable, чтобы сгенерировать другой частичный результат.
- Повторяйте процесс до тех пор, пока не будет исчерпана повторяемость, а затем верните единственное суммарное значение.
Идея, лежащая в основе reduce() Python, заключается в том, чтобы взять существующую функцию, применить ее совокупно ко всем элементам в iterable и сгенерировать одно конечное значение. В общем, reduce() в Python удобен для обработки итераций без написания явных циклов for. Поскольку reduce() написан на C, его внутренний цикл может быть быстрее, чем явный цикл Python for.
Функция reduce() в Python изначально была встроенной (и до сих пор используется в Python 2.x), но она была перенесена в functools.reduce() в Python 3.0. Это решение было основано на некоторых возможных проблемах с производительностью и удобочитаемостью.
Еще одной причиной перехода reduce() на functools стало внедрение встроенных функций, таких как sum(), any(), all(), max(), min(), и len(), которые предоставляют более эффективные, читабельные и основанные на Python способы решения общих задач для reduce(). Вы узнаете, как использовать их вместо reduce() далее в руководстве.
В Python 3.x, если вам нужно использовать reduce(), то сначала вам нужно импортировать функцию в вашу текущую область , используя import сформулируйте одним из следующих способов:
import functoolsа затем используйте полных имен, напримерfunctools.reduce().from functools import reduceа затем вызовитеreduce()напрямую.
Согласно документации для reduce() функция имеет следующую сигнатуру:
functools.reduce(function, iterable[, initializer])
В документации Python также указано, что reduce() примерно эквивалентно следующей функции Python:
def reduce(function, iterable, initializer=None):
it = iter(iterable)
if initializer is None:
value = next(it)
else:
value = initializer
for element in it:
value = function(value, element)
return value
Как и эта функция Python, reduce() работает путем применения функции с двумя аргументами к элементам iterable в цикле слева направо, в конечном итоге сводя iterable к одному суммарному value.
reduce() в Python также принимает третий необязательный аргумент, называемый initializer, который предоставляет начальное значение для вычисления или сокращения.
В следующих двух разделах вы подробно рассмотрите, как работает reduce() в Python, и значение, стоящее за каждым из его аргументов.
Необходимые аргументы: function и iterable
Первым аргументом функции Python reduce() является функция с двумя аргументами, которую удобно называть function. Эта функция будет применяться к элементам в iterable для кумулятивного вычисления конечного значения.
Несмотря на то, что в официальной документации говорится о первом аргументе reduce() как о “функции с двумя аргументами”, вы можете передать любой вызываемый объект Python в reduce() до тех пор, пока вызываемый объект принимает два аргумента. Вызываемые объекты включают классов, экземпляров, которые реализуют специальный метод, вызываемый __call__(), методы экземпляра, методы класса, статические методы и функции.
Примечание: Для получения более подробной информации о вызываемых объектах Python вы можете ознакомиться с документацией по Python и прокрутить вниз до “Вызываемые типы".”
Второй обязательный аргумент, iterable, будет принимать любой итерационный параметр Python, как следует из его названия. Сюда входят списки, кортежи, range объекты, генераторы, итераторы, наборы, словарь ключей и значений, а также любых других объектов Python, по которым вы можете выполнять итерации.
Примечание: Если вы передадите итератор в Python's reduce(), то функции нужно будет обработать итератор, прежде чем вы сможете получить окончательное значение. Таким образом, имеющийся под рукой итератор не останется ленивым.
Чтобы понять, как работает reduce(), вам нужно написать функцию, которая вычисляет сумму двух чисел и выводит математическая операция, эквивалентная отображению на экране. Вот код:
>>> def my_add(a, b):
... result = a + b
... print(f"{a} + {b} = {result}")
... return result
Эта функция вычисляет сумму a и b, выводит сообщение с операцией, используя f-строку, и возвращает результат вычисление. Вот как это работает:
>>> my_add(5, 5)
5 + 5 = 10
10
my_add() это функция с двумя аргументами, поэтому вы можете передать ее в Python reduce() вместе с iterable для вычисления суммарной суммы элементов в iterable. Проверьте следующий код, в котором используется список чисел:
>>> from functools import reduce
>>> numbers = [0, 1, 2, 3, 4]
>>> reduce(my_add, numbers)
0 + 1 = 1
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10
Когда вы вызываете reduce(), передавая my_add() и numbers в качестве аргументов, вы получаете выходные данные, которые показывают все операции, которые выполняет reduce(), чтобы получить окончательный результат 10. В этом случае операции эквивалентны ((((0 + 1) + 2) + 3) + 4) = 10.
Вызов reduce() в приведенном выше примере применяет my_add() к первым двум элементам в numbers (0 и 1) и в качестве результата получает 1. Затем reduce() вызывает my_add(), используя 1 и следующий элемент в numbers (который является 2) в качестве аргументов, получая 3 в качестве результат. Процесс повторяется до тех пор, пока в numbers не закончатся элементы, и reduce() возвращает окончательный результат 10.
Необязательный аргумент: initializer
Третий аргумент в Python reduce(), называемый initializer, является необязательным. Если вы укажете значение в initializer, то reduce() передаст его при первом вызове function в качестве первого аргумента.
Это означает, что первый вызов function будет использовать значение initializer и первый элемент iterable для выполнения первого частичного вычисления. После этого reduce() продолжает работу с последующими элементами iterable.
Вот пример, в котором вы используете my_add() с initializer равным 100:
>>> from functools import reduce
>>> numbers = [0, 1, 2, 3, 4]
>>> reduce(my_add, numbers, 100)
100 + 0 = 100
100 + 1 = 101
101 + 2 = 103
103 + 3 = 106
106 + 4 = 110
110
Поскольку вы указываете значение 100 в initializer, reduce() в Python использует это значение при первом вызове в качестве первого аргумента в my_add(). Обратите внимание, что на первой итерации my_add() использует 100 и 0, которые являются первым элементом numbers, для выполнения вычисления 100 + 0 = 100.
Еще один момент, на который следует обратить внимание, заключается в том, что если вы укажете значение initializer, то reduce() выполнит на одну итерацию больше, чем это было бы без initializer.
Если вы планируете использовать reduce() для обработки повторяющихся значений, которые потенциально могут быть пустыми, рекомендуется указывать значение initializer. Функция reduce() в Python будет использовать это значение в качестве возвращаемого значения по умолчанию, когда iterable пусто. Если вы не укажете значение initializer, то значение reduce() будет равно TypeError. Взгляните на следующий пример:
>>> from functools import reduce
>>> # Using an initializer value
>>> reduce(my_add, [], 0) # Use 0 as return value
0
>>> # Using no initializer value
>>> reduce(my_add, []) # Raise a TypeError with an empty iterable
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: reduce() of empty sequence with no initial value
Если вы вызываете reduce() с пустым iterable, то функция вернет значение, указанное в initializer. Если вы не укажете initializer, то reduce() вызовет TypeError при обработке пустых итераций.
Примечание: Чтобы глубже понять, что такое обратная трассировка Python, ознакомьтесь с Разделом "Понимание обратной трассировки Python".
Теперь, когда вы знакомы с принципом работы reduce(), вы готовы научиться применять его к некоторым распространенным задачам программирования.
Сокращение числа итераций с помощью Python reduce()
Итак, вы узнали, как работает функция reduce() в Python и как с ее помощью сократить число итераций с помощью пользовательской функции. Вы также узнали значение каждого аргумента для reduce() и как они работают.
В этом разделе вы рассмотрите некоторые распространенные варианты использования reduce() и способы их решения с помощью функции. Вы также узнаете о некоторых альтернативных инструментах Python, которые можно использовать вместо reduce(), чтобы сделать ваш код более понятным, эффективным и читабельным.
Суммирование числовых значений
"Hello, World!" в Python reduce() - это вариант использования суммы . Он включает в себя вычисление суммарной суммы списка чисел. Допустим, у вас есть список чисел типа [1, 2, 3, 4]. Его сумма будет равна 1 + 2 + 3 + 4 = 10. Вот краткий пример того, как решить эту проблему с помощью цикла Python for:
>>> numbers = [1, 2, 3, 4]
>>> total = 0
>>> for num in numbers:
... total += num
...
>>> total
10
Цикл for выполняет итерацию по каждому значению в numbers и накапливает их в total. Конечным результатом является сумма всех значений, которая в данном примере равна 10. Переменная , используемая как total в этом примере, иногда называется аккумулятором .
Это, пожалуй, самый распространенный вариант использования reduce() в Python. Чтобы реализовать эту операцию с помощью reduce(), у вас есть несколько вариантов. Некоторые из них включают использование reduce() с одной из следующих функций:
- A определяемая пользователем функция
- А
lambdaфункция - Вызываемая функция
operator.add()
Чтобы использовать пользовательскую функцию, вам нужно закодировать функцию, которая складывает два числа. Затем вы можете использовать эту функцию с reduce(). Для этого примера вы можете переписать my_add() следующим образом:
>>> def my_add(a, b):
... return a + b
...
>>> my_add(1, 2)
3
my_add() добавляет два числа, a и b, и возвращает результат. Используя my_add(), вы можете использовать reduce() для вычисления суммы значений в итерационной таблице Python. Вот как:
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> reduce(my_add, numbers)
10
Вызов reduce() применяет my_add() к элементам в numbers для вычисления их суммарной суммы. Конечный результат равен 10, как и ожидалось.
Вы также можете выполнить те же вычисления, используя функцию lambda. В этом случае вам нужна функция lambda, которая принимает два числа в качестве аргументов и возвращает их сумму. Взгляните на следующий пример:
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> reduce(lambda a, b: a + b, numbers)
10
Функция lambda принимает два аргумента и возвращает их сумму. reduce() применяет функцию lambda в цикле для вычисления суммарной суммы элементов в numbers.
Аналогично, вы можете воспользоваться преимуществами модуля Python operator. Этот модуль экспортирует набор функций, соответствующих встроенным операторам Python. Для решения рассматриваемой задачи вы можете использовать operator.add() вместе с reduce() в Python. Посмотрите на следующий пример:
>>> from operator import add
>>> from functools import reduce
>>> add(1, 2)
3
>>> numbers = [1, 2, 3, 4]
>>> reduce(add, numbers)
10
В этом примере add() принимает два аргумента и возвращает их сумму. Итак, вы можете использовать add() с reduce() для вычисления суммы всех элементов numbers. Поскольку add() написано на C и оптимизировано для повышения эффективности, это может быть вашим лучшим выбором при использовании reduce() для решения задачи sum. Обратите внимание, что использование operator.add() также более удобно для чтения, чем использование функции lambda.
Вариант использования sum настолько распространен в программировании, что Python, начиная с версии 2.3, включил специальную встроенную функцию sum() для ее решения. sum() объявляется как sum(iterable[, start]).
start является необязательным аргументом для sum() и по умолчанию имеет значение 0. Функция добавляет значение start к элементам iterable слева направо и возвращает итоговое значение. Взгляните на следующий пример:
>>> numbers = [1, 2, 3, 4]
>>> sum(numbers)
10
Поскольку sum() является встроенной функцией, вам не нужно ничего импортировать. Она всегда доступна для вас. Использование sum() является наиболее простым способом решения задачи sum. Он чистый, читабельный и лаконичный. Он основан на основном принципе Python:
Простое лучше сложного. (Источник)
Добавление sum() в язык стало большим преимуществом с точки зрения удобства чтения и производительности по сравнению с использованием reduce() или for цикла.
Примечание: Для получения более подробной информации о сравнении производительности reduce() с производительностью других инструментов сокращения Python ознакомьтесь с разделом Производительность - это Ключ.
Если вы имеете дело с вариантом использования sum, то передовая практика рекомендует использовать sum().
Умножение числовых значений
Вариант использования продукта в Python reduce() очень похож на вариант использования суммы, но на этот раз операция заключается в умножении. Другими словами, вам нужно вычислить произведение всех значений в итерационной таблице.
Например, предположим, что у вас есть список [1, 2, 3, 4]. Его результатом будет 1 * 2 * 3 * 4 = 24. Вы можете рассчитать это, используя цикл Python for. Посмотрите на следующий пример:
>>> numbers = [1, 2, 3, 4]
>>> product = 1
>>> for num in numbers:
... product *= num
...
>>> product
24
Цикл выполняет итерацию по элементам в numbers, умножая каждый элемент на результат предыдущей итерации. В этом случае начальное значение для аккумулятора product должно быть 1 вместо 0. Поскольку любое число, умноженное на ноль, равно нулю, начальное значение 0 всегда будет равнять ваш результат 0.
Это вычисление также является довольно популярным вариантом использования reduce() в Python. И снова, вы рассмотрите три способа решения проблемы. Вы будете использовать reduce() с помощью:
- Определяемая пользователем функция
- Функция
lambda - Вызываемая функция
operator.mul()
Для варианта 1 вам нужно будет создать пользовательскую функцию, которая принимает два аргумента и возвращает их произведение. Затем вы будете использовать эту функцию с reduce() для вычисления произведения элементов в iterable. Взгляните на следующий код:
>>> from functools import reduce
>>> def my_prod(a, b):
... return a * b
...
>>> my_prod(1, 2)
2
>>> numbers = [1, 2, 3, 4]
>>> reduce(my_prod, numbers)
24
Функция my_prod() умножает два числа: a и b. Вызов reduce() выполняет итерацию по элементам numbers и вычисляет их результат, применяя my_prod() к последующим элементам. Конечным результатом является произведение всех элементов в numbers, которое в данном примере равно 24.
Если вы предпочитаете использовать функцию lambda для решения этого варианта использования, то вам нужна функция, которая принимает два аргумента и возвращает их результат. Вот пример:
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> reduce(lambda a, b: a * b, numbers)
24
Анонимная функция совершает волшебство, умножая последовательные элементы, в то время как reduce() выполняет итерацию по numbers. Опять же, результатом является совокупность всех элементов в numbers.
Вы также можете использовать operator.mul() для решения проблемы использования продукта. operator.mul() принимает два числа и возвращает результат их умножения. Это подходящая функциональность для решения поставленной задачи. Посмотрите на следующий пример:
>>> from operator import mul
>>> from functools import reduce
>>> mul(2, 2)
4
>>> numbers = [1, 2, 3, 4]
>>> reduce(mul, numbers)
24
Поскольку mul() сильно оптимизирован, ваш код будет работать лучше, если вы будете использовать эту функцию, а не пользовательскую функцию или функцию lambda. Обратите внимание, что это решение также гораздо более удобочитаемо.
Наконец, если вы используете Python 3.8, то у вас есть доступ к более понятному на языке Python решению для этого варианта использования. В Python 3.8 добавлена новая функция под названием prod(),, которая находится в модуле Python math. Эта функция аналогична sum(), но возвращает произведение значения start, умноженного на число iterable.
В случае math.prod() аргумент start является необязательным и по умолчанию имеет значение 1. Вот как это работает:
>>> from math import prod
>>> numbers = [1, 2, 3, 4]
>>> prod(numbers)
24
Это также большой выигрыш с точки зрения удобочитаемости и эффективности по сравнению с использованием reduce(). Итак, если вы используете Python 3.8 и сокращение продукта является обычной операцией в вашем коде, то вам будет удобнее использовать math.prod(), а не reduce() в Python.
Нахождение минимального и максимального значений
Задача найти минимальное и максимальное значение в итерационной переменной также является проблемой сокращения, которую вы можете решить, используя reduce() в Python. Идея состоит в том, чтобы сравнить элементы в iterable, чтобы найти минимальное или максимальное значение.
Допустим, у вас есть список чисел [3, 5, 2, 4, 7, 1]. В этом списке минимальное значение равно 1, а максимальное - 7. Чтобы найти эти значения, вы можете использовать цикл Python for. Проверьте следующий код:
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> # Minimum
>>> min_value, *rest = numbers
>>> for num in rest:
... if num < min_value:
... min_value = num
...
>>> min_value
1
>>> # Maximum
>>> max_value, *rest = numbers
>>> for num in rest:
... if num > max_value:
... max_value = num
...
>>> max_value
7
Оба цикла выполняют итерацию по элементам в rest и обновляют значение min_value или max_value в соответствии с результатом последовательных сравнений. Обратите внимание, что изначально min_value и max_value содержат число 3, которое является первым значением в numbers. Переменная rest содержит остальные значения в numbers. Другими словами, rest = [5, 2, 4, 7, 1].
Примечание: В приведенных выше примерах вы используете повторяющийся оператор распаковки Python (*) для распакуйте или разложите значения в numbers на две переменные. В первом случае конечным результатом является то, что min_value получает первое значение из numbers, которое равно 3, а rest собирает остальные значения в список.
Ознакомьтесь с подробностями в следующих примерах:
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> min_value, *rest = numbers
>>> min_value
3
>>> rest
[5, 2, 4, 7, 1]
>>> max_value, *rest = numbers
>>> max_value
3
>>> rest
[5, 2, 4, 7, 1]
Оператор распаковки Python iterable (*) полезен, когда вам нужно распаковать последовательность или итерацию в несколько переменных.
Для лучшего понимания операций распаковки в Python вы можете ознакомиться с PEP 3132 Расширенная итеративная распаковка и PEP 448 Дополнительные обобщения для распаковки.
Теперь подумайте о том, как вы можете найти минимальное и максимальное значение в iterable, используя reduce() в Python. Опять же, вы можете использовать пользовательскую функцию или функцию lambda в зависимости от ваших потребностей.
В следующем коде реализовано решение, использующее две разные пользовательские функции. Первая функция принимает два аргумента, a и b, и возвращает их минимальное значение. Вторая функция будет использовать аналогичный процесс, но она вернет максимальное значение.
Вот функции и то, как вы можете использовать их с помощью reduce() в Python, чтобы найти минимальное и максимальное значение в итерируемом файле:
>>> from functools import reduce
>>> # Minimum
>>> def my_min_func(a, b):
... return a if a < b else b
...
>>> # Maximum
>>> def my_max_func(a, b):
... return a if a > b else b
...
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> reduce(my_min_func, numbers)
1
>>> reduce(my_max_func, numbers)
7
Когда вы запускаете reduce() с помощью my_min_func() и my_max_func(), вы получаете минимальное и максимальное значения в numbers соответственно. reduce() итераций перебирает элементы из numbers, сравнивает их суммарными парами и, наконец, возвращает минимальное или максимальное значение.
Примечание: Для реализации my_min_func() и my_max_func() вы использовали условное выражение Python, или троичный оператор, в качестве значения return. Для более глубокого понимания того, что такое условные выражения и как они работают, ознакомьтесь с Условными операторами в Python (if/elif/else).
Вы также можете использовать функцию lambda для решения задачи о минимуме и максимуме. Взгляните на следующие примеры:
>>> from functools import reduce
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> # Minimum
>>> reduce(lambda a, b: a if a < b else b, numbers)
1
>>> # Maximum
>>> reduce(lambda a, b: a if a > b else b, numbers)
7
На этот раз вы используете две функции lambda, которые определяют, является ли a меньше или больше b. В этом случае reduce() в Python применяет функцию lambda к каждому значению в numbers, сравнивая его с результатом предыдущего вычисления. В конце процесса вы получаете минимальное или максимальное значение.
Проблема минимума и максимума настолько распространена в программировании, что Python добавил встроенные функции для выполнения этих сокращений. Эти функции удобно называются min() и max(), и вам не нужно ничего импортировать, чтобы иметь возможность их использовать . Вот как они работают:
>>> numbers = [3, 5, 2, 4, 7, 1]
>>> min(numbers)
1
>>> max(numbers)
7
Когда вы используете min() и max() для поиска минимального и максимального элемента в итерируемом коде, ваш код становится намного более читабельным по сравнению с использованием reduce() в Python. Кроме того, поскольку min() и max() являются высокооптимизированными функциями языка Си, вы также можете сказать, что ваш код будет более эффективным.
Итак, когда дело доходит до решения этой проблемы в Python, лучше всего использовать min() и max(), а не reduce().
Проверка истинности всех значений
Вариант использования для всех значений true в Python reduce() включает в себя определение того, являются ли все элементы в iterable истинными или нет. Чтобы решить эту проблему, вы можете использовать reduce() вместе с пользовательской функцией или функцией lambda.
Вы начнете с кодирования цикла for, чтобы выяснить, все ли элементы в итерируемой переменной являются истинными. Вот код:
>>> def check_all_true(iterable):
... for item in iterable:
... if not item:
... return False
... return True
...
>>> check_all_true([1, 1, 1, 1, 1])
True
>>> check_all_true([1, 1, 1, 1, 0])
False
>>> check_all_true([])
True
Если все значения в iterable равны true, то check_all_true() возвращает True. В противном случае возвращается False. Он также возвращает True с пустыми итерациями. check_all_true() реализует оценку короткого замыкания. Это означает, что функция возвращает значение, как только находит значение false, без обработки остальных элементов в iterable.
Чтобы решить эту проблему с помощью reduce() в Python, вам нужно написать функцию, которая принимает два аргумента и возвращает True, если оба аргумента равны true. Если один или оба аргумента равны false, то функция вернет False. Вот код:
>>> def both_true(a, b):
... return bool(a and b)
...
>>> both_true(1, 1)
True
>>> both_true(1, 0)
False
>>> both_true(0, 0)
False
Эта функция принимает два аргумента: a и b. Затем вы используете оператор and, чтобы проверить, являются ли оба аргумента истинными. Возвращаемое значение будет True, если оба аргумента истинны. В противном случае, это будет False.
В Python следующие объекты считаются ложными:
- Константы, такие как
NoneиFalse - Числовые типы с нулевым значением, например
0,0.0,0j,Decimal(0), иFraction(0, 1) - Пустые последовательности и коллекции, такие как
"",(),[],{},set(), иrange(0) - Объектов, которые реализуют
__bool__()с возвращаемым значениемFalseили__len__()с возвращаемым значением0
Любой другой объект будет считаться истинным.
Вам нужно использовать bool() для преобразования возвращаемого значения and в True или False. Если вы не используете bool(), то ваша функция будет вести себя не так, как ожидалось, потому что and возвращает один из объектов в выражении вместо True или False. Ознакомьтесь со следующими примерами:
>>> a = 0
>>> b = 1
>>> a and b
0
>>> a = 1
>>> b = 2
>>> a and b
2
and возвращает первое значение в выражении, если оно равно false. В противном случае возвращает последнее значение в выражении независимо от его истинностного значения. Вот почему в этом случае вам нужно использовать bool(). bool() возвращает логическое значение (True или False) , полученное в результате вычисления логического выражения или объекта. Ознакомьтесь с примерами, используя bool():
>>> a = 0
>>> b = 1
>>> bool(a and b)
False
>>> a = 1
>>> b = 2
>>> bool(a and b)
True
bool() всегда будет возвращать либо True, либо False после вычисления соответствующего выражения или объекта.
Примечание: Чтобы лучше понять операторы и выражения Python, вы можете ознакомиться с Операторами и выражениями в Python.
Вы можете передать both_true() в reduce(), чтобы проверить, все ли элементы итерируемой переменной являются истинными или нет. Вот как это работает:
>>> from functools import reduce
>>> reduce(both_true, [1, 1, 1, 1, 1])
True
>>> reduce(both_true, [1, 1, 1, 1, 0])
False
>>> reduce(both_true, [], True)
True
Если вы передадите both_true() в качестве аргумента reduce(), то вы получите True, если все элементы в итерируемой переменной равны true. В противном случае вы получите False.
В третьем примере вы передаете True в initializer из reduce(), чтобы получить то же поведение, что и check_all_true(), и избежать TypeError.
Вы также можете использовать функцию lambda для решения полностью верного варианта использования reduce(). Вот несколько примеров:
>>> from functools import reduce
>>> reduce(lambda a, b: bool(a and b), [0, 0, 1, 0, 0])
False
>>> reduce(lambda a, b: bool(a and b), [1, 1, 1, 2, 1])
True
>>> reduce(lambda a, b: bool(a and b), [], True)
True
Эта функция lambda очень похожа на both_true() и использует то же выражение в качестве возвращаемого значения. Она возвращает True, если оба аргумента равны true. В противном случае он возвращается False.
Обратите внимание, что в отличие от check_all_true(), когда вы используете reduce() для решения полностью верного варианта использования, нет оценки короткого замыкания, потому что reduce() не возвращается, пока не пройдет весь сценарий. повторяемый. Это может увеличить время обработки вашего кода.
Например, предположим, что у вас есть список lst = [1, 0, 2, 0, 0, 1] и вам нужно проверить, все ли элементы в lst соответствуют действительности. В этом случае check_all_true() завершит работу, как только его цикл обработает первую пару элементов (1 и 0), поскольку 0 имеет значение false. Вам не нужно продолжать повторение, потому что у вас уже есть решение поставленной задачи.
С другой стороны, решение reduce() не завершится, пока не будут обработаны все элементы в lst. Это произойдет пятью итерациями позже. А теперь представьте, как это повлияло бы на производительность вашего кода, если бы вы обрабатывали большой итерационный массив!
К счастью, Python предоставляет правильный инструмент для решения этой проблемы простым, понятным и эффективным способом: встроенную функцию all().
Вы можете использовать all(iterable), чтобы проверить, все ли пункты в iterable соответствуют действительности. Вот как работает all():
>>> all([1, 1, 1, 1, 1])
True
>>> all([1, 1, 1, 0, 1])
False
>>> all([])
True
all() перебирает элементы в цикле iterable, проверяя истинность каждого из них. Если all() находит ложный элемент, то возвращает False. В противном случае он возвращает True. Если вы вызываете all() с пустой итерацией, то вы получаете True, потому что в пустой итерации нет элемента false.
all() это функция языка Си, оптимизированная для повышения производительности. Эта функция также реализована с использованием метода оценки короткого замыкания. Итак, если вы имеете дело с проблемой all-true в Python, то вам следует рассмотреть возможность использования all() вместо reduce().
Проверка того, является ли какое-либо значение Истинным
Другим распространенным вариантом использования reduce() в Python является вариант использования any-true. На этот раз вам нужно выяснить, является ли хотя бы один элемент в iterable истинным. Чтобы решить эту проблему, вам нужно написать функцию, которая принимает значение iterable и возвращает True, если какой-либо элемент в iterable равен true, и False в противном случае. Взгляните на следующую реализацию этой функции:
>>> def check_any_true(iterable):
... for item in iterable:
... if item:
... return True
... return False
...
>>> check_any_true([0, 0, 0, 1, 0])
True
>>> check_any_true([0, 0, 0, 0, 0])
False
>>> check_any_true([])
False
Если хотя бы один элемент в iterable равен true, то check_any_true() возвращает True. Он возвращает False только в том случае, если все элементы имеют значение false или если повторяемая строка пуста. Эта функция также реализует оценку короткого замыкания, поскольку она возвращает результат, как только находит истинное значение, если таковое имеется.
Чтобы решить эту проблему с помощью reduce() в Python, вам нужно закодировать функцию, которая принимает два аргумента и возвращает True, если хотя бы один из них равен true. Если оба значения равны false, то функция должна возвращать False.
Вот возможная реализация этой функции:
>>> def any_true(a, b):
... return bool(a or b)
...
>>> any_true(1, 0)
True
>>> any_true(0, 1)
True
>>> any_true(0, 0)
False
any_true() возвращает True, если хотя бы один из его аргументов равен true. Если оба аргумента ложны, то any_true() возвращает False. Как и в случае с both_true() в предыдущем разделе, any_true() использует bool() для преобразования результата выражения a or b в True или False.
Оператор Python or работает немного иначе, чем and. Он возвращает первый истинный объект или последний объект в выражении. Ознакомьтесь со следующими примерами:
>>> a = 1
>>> b = 2
>>> a or b
1
>>> a = 0
>>> b = 1
>>> a or b
1
>>> a = 0
>>> b = []
>>> a or b
[]
Оператор Python or возвращает первый объект true или, если оба значения false, последний объект. Итак, вам также нужно использовать bool(), чтобы получить согласованное возвращаемое значение из any_true().
Как только вы настроите эту функцию, вы можете продолжить сокращение. Взгляните на следующие вызовы для reduce():
>>> from functools import reduce
>>> reduce(any_true, [0, 0, 0, 1, 0])
True
>>> reduce(any_true, [0, 0, 0, 0, 0])
False
>>> reduce(any_true, [], False)
False
Вы решили проблему, используя reduce() в Python. Обратите внимание, что в третьем примере вы передаете False в инициализатор reduce(), чтобы воспроизвести поведение исходного check_any_true(), а также избежать ошибки. TypeError.
Примечание: Как и в примерах из предыдущего раздела, в этих примерах из reduce() не выполняется оценка короткого замыкания. Это означает, что они могут повлиять на производительность вашего кода.
Вы также можете использовать функцию lambda с reduce() для решения любого варианта использования. Вот как это можно сделать:
>>> from functools import reduce
>>> reduce(lambda a, b: bool(a or b), [0, 0, 1, 1, 0])
True
>>> reduce(lambda a, b: bool(a or b), [0, 0, 0, 0, 0])
False
>>> reduce(lambda a, b: bool(a or b), [], False)
False
Эта функция lambda очень похожа на any_true(). Она возвращает True, если любой из ее двух аргументов равен true. Если оба аргумента равны false, то он возвращает False.
Несмотря на то, что это решение занимает всего одну строку кода, оно все равно может сделать ваш код нечитаемым или, по крайней мере, трудным для понимания. Опять же, Python предоставляет инструмент для эффективного решения любой проблемы без использования reduce(): встроенную функцию any().
any(iterable) перебирает элементы в iterable, проверяя истинностное значение каждого из них, пока не найдет истинный элемент. Функция возвращает True, как только находит истинное значение. Если any() не находит истинного значения, то возвращает False. Вот пример:
>>> any([0, 0, 0, 0, 0])
False
>>> any([0, 0, 0, 1, 0])
True
>>> any([])
False
Опять же, вам не нужно импортировать any(), чтобы использовать его в своем коде. any() работает так, как ожидалось. Возвращает False, если все элементы в iterable имеют значение false. В противном случае он возвращает True. Обратите внимание, что если вы вызываете any() с пустой итерацией, то вы получаете False, потому что в пустой итерации нет истинного элемента.
Как и в случае с all(), any() это функция C, оптимизированная для повышения производительности. Она также реализована с использованием оценки короткого замыкания. Итак, если вы имеете дело с проблемой any-true в Python, то рассмотрите возможность использования any() вместо reduce().
Сравнивая reduce() и accumulate()
Функция Python, называемая accumulate(), находится в itertools и ведет себя аналогично reduce(). accumulate(iterable[, func]) принимает один обязательный аргумент, iterable, который может быть любым, повторяемым в Python. Необязательный второй аргумент, func, должен быть функцией (или вызываемым объектом), которая принимает два аргумента и возвращает одно значение.
accumulate() возвращает итератор. Каждый элемент в этом итераторе будет суммированным результатом вычисления, которое выполняет func. Вычислением по умолчанию является сумма. Если вы не предоставите функцию для accumulate(), то каждый элемент в результирующем итераторе будет представлять собой накопленную сумму предыдущих элементов в iterable плюс текущий элемент.
Ознакомьтесь со следующими примерами:
>>> from itertools import accumulate
>>> from operator import add
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> list(accumulate(numbers))
[1, 3, 6, 10]
>>> reduce(add, numbers)
10
Обратите внимание, что последнее значение в результирующем итераторе - это то же значение, которое возвращает reduce(). В этом основное сходство между этими двумя функциями.
Примечание: Поскольку accumulate() возвращает итератор, вам нужно вызвать list(), чтобы использовать итератор и получить объект списка в качестве выходных данных.
Если, с другой стороны, вы предоставляете функцию с двумя аргументами (или вызываемую) в качестве аргумента func параметра accumulate(), то элементы в результирующем итераторе будут совокупным результатом выполненных вычислений по func. Вот пример, который использует operator.mul():
>>> from itertools import accumulate
>>> from operator import mul
>>> from functools import reduce
>>> numbers = [1, 2, 3, 4]
>>> list(accumulate(numbers, mul))
[1, 2, 6, 24]
>>> reduce(mul, numbers)
24
В этом примере вы снова можете видеть, что последний элемент в возвращаемом значении accumulate() равен значению, возвращаемому с помощью reduce().
С учетом производительности и удобочитаемости
У Python reduce() может быть очень низкая производительность, потому что он работает, вызывая функции несколько раз. Это может сделать ваш код медленным и неэффективным. Использование reduce() также может ухудшить читаемость вашего кода, когда вы используете его со сложными пользовательскими функциями или lambda функциями.
Из этого руководства вы узнали, что Python предлагает множество инструментов, которые могут изящно заменить reduce(), по крайней мере, в его основных вариантах использования. Вот основные выводы из прочитанного вами до этого момента:
-
Используйте специальную функцию для решения вариантов использования
reduce()Python, когда это возможно. Такие функции, какsum(),all(),any(),max(),min(),len(),math.prod(), и и т.д., сделают ваш код более быстрым, удобочитаемым, поддерживаемым и питоническим.. -
Избегайте сложных пользовательских функций при использовании
reduce(). Такого рода функции могут затруднить чтение и понимание вашего кода. Вместо этого вы можете использовать явный и легко читаемый циклfor. -
Избегайте сложных
lambdaфункций при использованииreduce(). Они также могут сделать ваш код нечитаемым и запутанным.
Второй и третий пункты вызвали беспокойство у самого Гвидо, когда он сказал следующее:
Итак, теперь
reduce(). На самом деле это то, что я всегда ненавидел больше всего, потому что, за исключением нескольких примеров, связанных с+или*, почти каждый раз, когда я вижу вызовreduce()с нетривиальным аргументом функции, мне нужно возьмите ручку и бумагу, чтобы нарисовать схему того, что на самом деле вводится в эту функцию, прежде чем я пойму, что должен делатьreduce(). Так что, на мой взгляд, применимостьreduce()в значительной степени ограничена ассоциативными операторами, а во всех остальных случаях лучше явно прописать цикл накопления. (Источник)
Следующие два раздела помогут вам реализовать этот общий совет в вашем коде. Они также содержат несколько дополнительных советов, которые помогут вам эффективно использовать reduce() Python, когда это действительно необходимо.
Производительность Является Ключевым Фактором
Если вы собираетесь использовать reduce() для решения задач, описанных в этом руководстве, то ваш код будет работать значительно медленнее по сравнению с кодом, использующим специальные встроенные функции. В следующих примерах вы будете использовать timeit.timeit() для быстрого измерения времени выполнения небольших фрагментов кода на Python и получения представления об их общей производительности.
timeit() принимает несколько аргументов, но для этих примеров вам нужно будет использовать только следующее:
stmtсодержит утверждение, что вам нужно время.setupпринимает дополнительные инструкции для общей настройки, напримерimportинструкции.globalsсодержит словарь, содержащий глобальное пространство имен, которое необходимо использовать для запускаstmt.
Взгляните на следующие примеры, в которых суммируется вариант использования с использованием reduce() различных инструментов и использованием sum() Python для сравнения:
>>> from functools import reduce
>>> from timeit import timeit
>>> # Using a user-defined function
>>> def add(a, b):
... return a + b
...
>>> use_add = "functools.reduce(add, range(100))"
>>> timeit(use_add, "import functools", globals={"add": add})
13.443158069014316
>>> # Using a lambda expression
>>> use_lambda = "functools.reduce(lambda x, y: x + y, range(100))"
>>> timeit(use_lambda, "import functools")
11.998800784000196
>>> # Using operator.add()
>>> use_operator_add = "functools.reduce(operator.add, range(100))"
>>> timeit(use_operator_add, "import functools, operator")
5.183870767941698
>>> # Using sum()
>>> timeit("sum(range(100))")
1.1643308430211619
Несмотря на то, что вы получите разные значения в зависимости от вашего оборудования, вы, скорее всего, получите наилучшее измерение времени, используя sum(). Эта встроенная функция также является наиболее читаемым и основанным на Python решением задачи суммирования.
Примечание: Для получения более подробной информации о том, как определять время вашего кода, ознакомьтесь с Функциями таймера Python: три способа мониторинга вашего кода.
Вторым лучшим вариантом было бы использовать reduce() с operator.add(). Функции в operator написаны на C и оптимизированы для повышения производительности. Таким образом, они должны работать лучше, чем пользовательская функция, функция lambda или цикл for.
Имеет значение удобочитаемость
Читаемость кода также является важной проблемой, когда речь заходит об использовании reduce() в Python. Несмотря на то, что reduce() обычно работает лучше, чем цикл Python for, как заявил сам Гвидо, чистый и Pythonic цикл часто проще выполнять, чем использовать reduce().
Руководство Что нового в Python 3.0 подтверждает эту идею, когда в нем говорится следующее:
Используйте
functools.reduce(), если вам это действительно нужно; однако в 99 процентах случаев явный циклforболее удобочитаем. (Источник)
Чтобы лучше понять важность удобочитаемости, представьте, что вы начинаете изучать Python и пытаетесь решить упражнение по вычислению суммы всех четных чисел в iterable.
Если вы уже знаете о reduce() Python и в прошлом занимались функциональным программированием, то, возможно, вам придет в голову следующее решение:
>>> from functools import reduce
>>> def sum_even(it):
... return reduce(lambda x, y: x + y if not y % 2 else x, it, 0)
...
>>> sum_even([1, 2, 3, 4])
6
В этой функции вы используете reduce() для кумулятивного суммирования четных чисел в итерационной таблице. Функция lambda принимает два аргумента, x и y, и возвращает их сумму, если они четные. В противном случае он возвращает x, который содержит результат предыдущей суммы.
Кроме того, вы устанавливаете для initializer значение 0, потому что в противном случае ваша сумма будет иметь начальное значение 1 (первое значение в iterable), что не соответствует четное число и внесет ошибку в вашу функцию.
Функция работает так, как вы ожидали, и вы довольны результатом. Однако вы продолжаете копаться в Python и узнаете о sum() и генераторных выражениях. Вы решили переработать свою функцию с помощью этих новых инструментов, и теперь ваша функция выглядит следующим образом:
>>> def sum_even(iterable):
... return sum(num for num in iterable if not num % 2)
...
>>> sum_even([1, 2, 3, 4])
6
Когда вы смотрите на этот код, вы по-настоящему гордитесь, и вам следует это сделать. Вы проделали отличную работу! Это прекрасная функция Python, которая читается почти как обычный английский. К тому же она эффективна и написана на языке Python. Что вы думаете?
Заключение
Функция reduce() в Python позволяет выполнять операции сокращения с повторяемыми объектами, используя вызываемые объекты Python и функции lambda. reduce() применяет функцию к элементам в iterable и сводит их к одному суммарному значению.
В этом уроке вы узнали:
- Что такое сокращение или сворачивание и когда это может быть полезно
- Как использовать Python
reduce()для решения распространенных задач сокращения, таких как суммирование или умножение чисел - Какие инструменты Python вы можете использовать для эффективной замены
reduce()в вашем коде
Обладая этими знаниями, вы сможете решить, какие инструменты наилучшим образом соответствуют вашим потребностям в программировании, когда дело доходит до решения задач сокращения в Python.
С годами reduce() был заменен другими инструментами на языке Python, такими как sum(), min(), max() all(), any() и другими. Однако reduce() все еще существует и по-прежнему популярен среди функциональных программистов. Если у вас есть вопросы или соображения по поводу использования reduce() или любой из его альтернатив на Python, обязательно напишите их в комментариях ниже.