- functools.reduce
- filter
- map
- map, filter, reduce и побочные эффекты
- reduce, map и filter и грязные функции
- Простое правило работы с ФВП
- Выводы
Встроенные функции высшего порядка map
, filter
и reduce
являются важными инструментами в Python для обработки коллекций данных. Они позволяют применять функции к элементам коллекции и получать новые значения.
В этом уроке мы научимся использовать встроенные функции map
, filter
и reduce
, чтобы обрабатывать коллекции данных в Python.
functools.reduce
Чтобы использовать функцию reduce
, необходимо импортировать ее из встроенного модуля functools
, который содержит функции высшего порядка.
Взглянем на объявление функции reduce
:
from functools import reduce
reduce(function, sequence[, initial]) -> value
Рассмотрим передаваемые аргументы подробнее:
function
— функция, которая будет применена к элементам коллекции для их объединенияsequence
— коллекция данных, которую нужно объединитьinitial
(опционально) — начальное значение аккумулятора, которое будет использовано в качестве первого аргумента при объединении
Если мы не указываем initial
, то функция reduce
будет использовать в качестве начального значения первый элемент передаваемой коллекции sequence
. Если коллекция пустая, то вызов reduce
завершится с ошибкой.
Пример работы функции functools.reduce
:
from functools import reduce
numbers = [2, 3, 8]
def get_maximum(first_num, second_num):
return first_num if first_num > second_num else second_num
reduce(get_maximum, numbers, 10) # 10
reduce(get_maximum, numbers) # 8
Если указано начальное значение аккумулятора 10
, то результат будет 10
, так как это число больше всех элементов в коллекции. Если начальное значение не указано, то reduce
использует первый элемент в коллекции numbers
в качестве начального значения и возвращает максимальный элемент в коллекции.
Функция, которая передается в качестве первого аргумента в reduce
, должна принимать два аргумента и возвращать одно значение. При использовании reduce
нужно убедиться, что передаваемая функция ассоциативна — не зависит от порядка применения и может быть применена в любом порядке.
filter
Теперь взглянем на filter
:
filter(function or None, iterable) -> filter object
None
в роли предиката — это замена для условия «всё что угодно, лишь бы было истинно». Тот же результат даст фильтрация с bool
в качестве предиката.
Возвращаемое значение filter object
— это не список, но итератор. То есть это встроенный filter
— ленивый. Он возвращает элементы по одному, если находит подходящие.
Пример работы функции filter
:
numbers = [2, 3, 8, 15, 34, 42]
def is_even(num):
return num % 2 == 0
filter(is_even, numbers) # <filter object at ...>
list(filter(is_even, numbers)) # [2, 8, 34, 42]
Здесь функция is_even
проверяет, является ли число четным, и возвращает True
или False
в зависимости от этого. Функция filter
возвращает итератор, возвращающий только те элементы коллекции numbers
, для которых предикат is_even
возвращает True
.
Функция, которая передается в качестве первого аргумента в filter
, должна принимать один аргумент и возвращать значение True
или False
. При использовании filter
нужно убедиться, что предикат корректно определен и возвращает ожидаемый результат для каждого элемента коллекции.
Также стоит помнить, что filter
возвращает ленивый итератор. И чтобы получить все элементы, необходимо явно преобразовать его в список или другой тип коллекции.
map
Теперь взглянем на функцию map
:
map(func, *iterables) --> map object
Здесь map object
— тоже итератор. А аргумент *iterables
принимает несколько итерируемых объектов вместо одного.
Если map
передать более одного источника элементов, то функция func
будет вызвана сначала для всех первых элементов, а потом для остальных. И так будет продолжаться, пока не закончатся элементы хотя бы в одном источнике.
Это похоже на функцию zip
, которую мы упоминали в курсе по спискам. Только zip
всегда возвращает кортежи, а map
применяет произвольную функцию — от соответствующего числу источников количества аргументов.
Вот пример применения функции map
к паре источников:
from operator import mul
map(mul, "abc", [3, 5, 7]) # <map object at ...>
list(map(mul, "abc", [3, 5, 7])) # ['aaa', 'bbbbb', 'ccccccc']
Здесь мы передали функцию mul
из модуля operator
в качестве первого аргумента функции map
. mul
— это функция, которая принимает два аргумента и возвращает их произведение.
Функция map
вернула итератор, который мы затем преобразовали в список с помощью функции list
. Результатом выполнения функции map
является список строк, полученный умножением каждой буквы из "abc"
на соответствующее число из [3, 5, 7]
.
Стоит обратить внимание, что мы использовали *iterables
, чтобы передать несколько итерируемых объектов в map
.
map
, filter
, reduce
и побочные эффекты
Когда мы используем встроенные функции высшего порядка, следует руководствоваться следующим правилом: применяемые с их помощью функции по возможности должны быть чистыми.
Например, использование побочных эффектов вместе с функцией map
плохо тем, что функция возвращает ленивый итератор. Значит, эффекты будут происходить не при вызове, а когда мы будем потреблять элементы.
Еще побочные эффекты нарушают законы, которые характерны для математических версий map
, filter
и reduce
:
- Ассоциативность — результат применения функции не зависит от порядка применения. Например,
(f * g) * h
равносильноf * (g * h)
- Нейтральный элемент — существует такой элемент, при применении функции к которому результат не изменится. Например, для операции умножения этот элемент равен единице
- Дистрибутивность — результат применения функции к объединению двух множеств равен объединению результатов применения функции к каждому из множеств отдельно. Например,
f(S1 | S2) = f(S1) | f(S2)
- Коммутативность — результат не зависит от порядка аргументов. Например,
f(x, y) = f(y, x)
Если мы используем побочные эффекты в функциях map
, filter
и reduce
, то можем нарушить эти свойства и получить непредсказуемый результат. Например, если мы используем print
в функции map
, то порядок элементов в выводе может не соответствовать порядку элементов в исходной последовательности, что нарушает коммутативность.
Пример с нарушением ассоциативности:
from functools import reduce
def add(x, y):
print(f"Adding {x} and {y}")
return x + y
numbers = [1, 2, 3, 4, 2]
result = reduce(add, map(lambda x: x * 2, filter(lambda x: x % 2 == 0, numbers)))
# Adding 4 and 8
# Adding 12 and 4
# 16
Здесь мы использовали побочный эффект print
в функции add
, вызываемой внутри reduce
. Так порядок вызовов функции add
зависит от порядка обхода элементов в итераторах map
и filter
, что нарушает ассоциативность.
reduce
, map
и filter
и грязные функции
Рассмотрим использование грязных функций с reduce
, map
и filter
.
Грязные функции и reduce
Когда функции с побочными эффектами используются для правой и левой сверток, перестает работать равенство результата. В этом случае левая свертка будет производить эффекты для элементов по порядку, а правая — с конца к началу.
Если возвращаемый из функции результат сойдется, общий наблюдаемый эффект, например, порядок записанных в файл строк, будет отличаться.
Грязные функции и map
Если мы возьмем грязную функцию, то для map
нарушится закон: map(f, map(g, l)) == map(f*g, l)
.
Здесь в виде f*g
записана композиция функций — получение функции, делающей x -> f(g(x))
. Встроенного оператора для композиции в Python нет, но функцию композиции можно написать так:
def compose(f, g):
return lambda x: f(g(x))
Данный закон важен, так как позволяет произвести оптимизацию — превратить два прохода по последовательности в один. При использовании же функции с эффектами программа до оптимизации и после нее будет работать по-разному. Например, до оптимизации будут выполнены сначала все эффекты функции g
, потом — эффекты функции f
. А после оптимизации эффекты g
и f
будут происходить парами.
Грязные функции и filter
Для filter
возможна такая оптимизация:
filter(f, filter(g, l)) == filter(g&f, l)
Здесь g&f
— это условное обозначение функции, которая делает x -> g(x) and f(x)
. Такого оператора в Python нет, но реализовать подобную функцию можно так:
def filter_compose(f, g):
return lambda x: filter(f, filter(g, x))
В случае filter
после оптимизации эффекты тоже перемешаются, как было описано выше для функции map
.
Простое правило работы с ФВП
Если вы видите, что в ФВП просится не чистая функция — перепишите на цикл. Цикл у нас мощный, и в его теле может происходить что угодно. Поэтому пусть и эффекты происходят в цикле, а не в чистых map
, filter
и reduce
.
Оптимизация согласно законам конкретно во встроенных функциях может и не делаться. Но мы можем встретить или реализовать библиотеку, в которой подобные оптимизации будут возможны. Потом окажется, что сходу понять, почему код после оптимизации работает именно так, практически невозможно. А функции map
, filter
и reduce
как раз и нужны, чтобы упрощать понимание логики.
Выводы
Встроенные функции высшего порядка — это мощный инструмент для работы с коллекциями данных в Python. Они позволяют сократить количество кода и упростить его понимание, а также сделать код более читаемым и модульным.
map
, filter
и reduce
позволяют производить операции над итерируемыми объектами с помощью применения к ним заданных функций. Функции могут быть переданы как аргументы, что позволяет увеличить гибкость кода и переиспользовать функции.
При использовании этих функций следует руководствоваться принципами функционального программирования. Например, следует использовать чистые функции, которые не имеют побочных эффектов. Это позволяет избежать неожиданных результатов и сделать код более надежным и предсказуемым.
Также важно помнить, что map
и filter
возвращают ленивые итераторы, а reduce
возвращает только одно значение. При использовании этих функций необходимо быть осторожным и не забывать преобразовывать результаты к нужному типу данных.
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
- Статья «Как учиться и справляться с негативными мыслями»
- Статья «Ловушки обучения»
- Статья «Сложные простые задачи по программированию»
- Вебинар «Как самостоятельно учиться»
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.