Вступление
Функции map()
, filter()
и reduce()
привносят в Python немного
функционального программирования. Все три из них являются удобными
функциями, которые могут быть заменены
списками или циклами, но обеспечивают
более элегантный и краткий подход к некоторым проблемам.
Прежде чем продолжить, мы рассмотрим несколько вещей, с которыми вам следует ознакомиться, прежде чем читать о вышеупомянутых методах:
Что такое анонимная функция / метод или лямбда?
Анонимный метод - это метод без имени, то есть не привязанный к
идентификатору, как когда мы определяем метод с помощью def method:
Примечание. Хотя большинство людей используют термины «анонимная функция» и «лямбда-функция» как синонимы - это не одно и то же. Эта ошибка происходит потому , что в большинстве языков программирования лямбда является анонимной и всеми анонимными функции лямбда. То же самое и в Python. Таким образом, мы не будем вдаваться в подробности этого различия в этой статье.
Каков синтаксис лямбда-функции (или лямбда-оператора)?
lambda arguments: expression
Думайте о лямбдах как о однострочных методах без имени. Они работают практически так же, как и любой другой метод в Python, например:
def add(x,y):
return x + y
Может быть переведен на:
lambda x, y: x + y
Лямбда-выражения отличаются от обычных методов Python, потому что они
могут иметь только одно выражение, не могут содержать никаких
операторов, а их возвращаемый тип - объект function
Таким образом,
приведенная выше строка кода возвращает не значение x + y
а функцию,
которая вычисляет x + y
.
Почему лямбда-выражения имеют отношение к
map()
,filter()
иreduce()
?
Все три метода ожидают, что в качестве первого аргумента будет объект
function
Этот function
объект может быть предопределенным методом с
именем (например, def add(x,y)
).
Хотя чаще всего функции, переданные в map()
, filter()
и reduce()
, используются только один раз, поэтому часто нет смысла определять
функцию, на которую можно ссылаться.
Чтобы избежать определения новой функции для различных map()
/
filter()
/ reduce()
- более элегантным решением будет использование
короткой одноразовой анонимной функции, которую вы будете использовать
только один раз и никогда больше - лямбда.
Функция map ()
Функция map()
выполняет итерацию по всем элементам в данной итерации и
выполняет function
мы передали в качестве аргумента для каждого из
них.
Синтаксис:
map(function, iterable(s))
Мы можем передать столько итерируемых объектов, сколько захотим, после
передачи function
которую хотим использовать:
# Without using lambdas
def starts_with_A(s):
return s[0] == "A"
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(starts_with_A, fruit)
print(list(map_object))
Этот код приведет к:
[True, False, False, True, False]
Как мы видим, мы получили новый список, в котором функция
starts_with_A()
оценивалась для каждого элемента в списке fruit
.
Результаты этой функции добавлялись в список последовательно.
Более красивый способ сделать то же самое - использовать лямбды:
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
map_object = map(lambda s: s[0] == "A", fruit)
print(list(map_object))
Получаем тот же результат:
[True, False, False, True, False]
Примечание: вы могли заметить, что мы map_object
к списку для
печати значения каждого элемента. Мы сделали это, потому что вызов
print()
в списке распечатает фактические значения элементов. Вызов
print()
для map_object
этого распечатает адреса памяти значений.
Функция map()
возвращает map_object
, который является итерируемым,
и мы также могли бы напечатать такие результаты:
for value in map_object:
print(value)
Если вы хотите, чтобы map()
вместо этого возвращала список, вы можете
просто преобразовать его при вызове функции:
result_list = list(map(lambda s: s[0] == "A", fruit))
Функция filter ()
Подобно map()
, filter()
принимает function
и итерацию и создает
новый список.
Как следует из названия, filter()
формирует новый список, содержащий
только элементы, удовлетворяющие определенному условию, то есть
переданная function
True
.
Синтаксис:
filter(function, iterable(s))
Используя предыдущий пример, мы видим, что новый список будет содержать
только элементы, для которых starts_with_A()
возвращает True
:
# Without using lambdas
def starts_with_A(s):
return s[0] == "A"
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(starts_with_A, fruit)
print(list(filter_object))
Выполнение этого кода приведет к сокращению списка:
['Apple', 'Apricot']
Или переписать с использованием лямбды:
fruit = ["Apple", "Banana", "Pear", "Apricot", "Orange"]
filter_object = filter(lambda s: s[0] == "A", fruit)
print(list(filter_object))
Печать дает нам тот же результат:
['Apple', 'Apricot']
Функция reduce ()
reduce()
работает иначе, чем map()
и filter()
. Он не возвращает
новый список на основе function
и итерации. Вместо этого он возвращает
одно значение.
Кроме того, в Python 3 reduce()
больше не является встроенной
функцией, и ее можно найти в модуле functools
Синтаксис:
reduce(function, sequence[, initial])
reduce()
работает, вызывая function
мы передали для первых двух
элементов в последовательности. Результат, возвращаемый function
,
используется в другом вызове function
вместе со следующим (в данном
случае третьим) элементом.
Этот процесс повторяется, пока мы не пройдем все элементы последовательности.
Необязательный аргумент initial
используется, если он присутствует, в
начале этого «цикла» с первым элементом в первом вызове function
. В
некотором смысле, initial
элемент - это 0-й элемент перед первым, если
он указан.
reduce()
немного сложнее для понимания, чем map()
и filter()
,
поэтому давайте рассмотрим пошаговый пример:
-
Мы начинаем со списка
[2, 4, 7, 3]
и передаем функциюadd(x, y)
reduce()
вместе с этим списком безinitial
значения. -
reduce()
вызываетadd(2, 4)
, аadd()
возвращает6
-
reduce()
вызываетadd(6, 7)
(результат предыдущего вызоваadd()
и следующий элемент в списке в качестве параметров), аadd()
возвращает13
-
reduce()
вызываетadd(13, 3)
, аadd()
возвращает16
-
Поскольку в последовательности больше не осталось элементов,
reduce()
возвращает16
Единственная разница, если бы мы указали initial
значение, был бы
дополнительный шаг - 1,5. где reduce()
вызовет add(initial, 2)
и
будет использовать это возвращаемое значение на шаге 2 .
Давайте продолжим и воспользуемся функцией reduce()
from functools import reduce
def add(x, y):
return x + y
list = [2, 4, 7, 3]
print(reduce(add, list))
Выполнение этого кода даст:
16
Опять же, это можно было бы написать с помощью лямбда-выражений:
from functools import reduce
list = [2, 4, 7, 3]
print(reduce(lambda x, y: x + y, list))
print("With an initial value: " + str(reduce(lambda x, y: x + y, list, 10)))
И код приведет к:
16
With an initial value: 26
Заключение
Как упоминалось ранее, эти функции являются вспомогательными. Они существуют, поэтому вы можете избежать написания более громоздкого кода, но избегайте слишком частого использования и их, и лямбда-выражений.
Не применяйте эти инструменты принудительно, потому что «вы можете», так как это часто может привести к неразборчивому коду, который трудно поддерживать. Используйте их только тогда, когда совершенно ясно, что происходит, как только вы посмотрите на функцию или лямбда-выражение.
Если вы поймали себя на том, что пытаетесь уместить необходимую логику в
одну map()
или одно лямбда-выражение, гораздо лучше просто написать
немного более длинный метод for-loop / defined и избежать ненужной
путаницы в дальнейшем.