Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Встроенные map, filter, reduce Python: Функции

Встроенные функции высшего порядка 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:

  1. Ассоциативность — результат применения функции не зависит от порядка применения. Например, (f * g) * h равносильно f * (g * h)
  2. Нейтральный элемент — существует такой элемент, при применении функции к которому результат не изменится. Например, для операции умножения этот элемент равен единице
  3. Дистрибутивность — результат применения функции к объединению двух множеств равен объединению результатов применения функции к каждому из множеств отдельно. Например, f(S1 | S2) = f(S1) | f(S2)
  4. Коммутативность — результат не зависит от порядка аргументов. Например, 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 возвращает только одно значение. При использовании этих функций необходимо быть осторожным и не забывать преобразовывать результаты к нужному типу данных.


Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка веб-приложений на Django
10 месяцев
с нуля
Старт 25 апреля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»