map(), filter() и reduce() в Python: функции высшего порядка
Этот урок переведён с английского оригинала.Read in EnglishLeer en españolСообщить об ошибке в переводе
Представьте сборочный конвейер на заводе. С одного конца поступает сырьё. На первом посту каждой детали придают форму. На следующем отбраковывают дефектные детали. На последнем все годные детали собирают в одно готовое изделие.
В Python есть три функции, которые работают точно как такой конвейер: map() преобразует каждый элемент, filter() оставляет только нужные элементы, а reduce() объединяет всё в один результат. Вместе они позволяют обрабатывать коллекции данных аккуратно и понятно.
В этом уроке вы узнаете, как работают эти инструменты, когда их применять и когда списковое включение (list comprehension) окажется лучшим выбором. К концу урока вы будете строить конвейеры обработки данных, которыми гордился бы любой мастер цеха.
Что такое функции высшего порядка?
Функция высшего порядка принимает другую функцию в качестве аргумента или возвращает функцию как результат. Вот и всё.
Вы уже пользовались такой функцией, сами того не замечая. У встроенной функции sorted() есть параметр key, в который передаётся функция:
['apple', 'banana', 'cherry']
Здесь len передаётся в sorted() как аргумент. Функция sorted вызывает len для каждого элемента, чтобы определить порядок. Вы не вызывали len() сами, а только сообщили sorted, какую функцию использовать.
map(), filter() и reduce() работают так же. Вы передаёте им функцию и коллекцию, и они применяют эту функцию к элементам. Именно поэтому они считаются функциями высшего порядка.
map(): преобразуем каждый элемент
map() принимает функцию и итерируемый объект (например, список), применяет функцию к каждому элементу и возвращает преобразованные результаты. Это «пост обработки» на нашем конвейере.
Синтаксис такой: map(function, iterable). Функция возвращает объект map, который обычно преобразуют в список.
[2, 4, 6, 8, 10]
map(double, numbers) вызывает double(1), double(2), double(3) и так далее, собирая все результаты. Обёртка list() превращает объект map в обычный список, чтобы его можно было вывести.
Писать целую функцию только ради удвоения числа слишком громоздко, поэтому map() часто используют вместе с lambda, крошечной анонимной функцией:
[2, 4, 6, 8, 10] ['ALICE', 'BOB', 'CHARLIE'] [10, 20, 6]
В map() можно передать любую функцию: вашу собственную, lambda или встроенную, например str.upper, round, len или int.
filter(): оставляем только нужное
filter() принимает функцию и итерируемый объект. Функция должна возвращать True или False для каждого элемента. Остаются только те элементы, для которых функция вернула True. Это контроль качества на конвейере: бракованные детали убирают.
[2, 4, 6, 8, 10]
filter(is_even, numbers) вызывает is_even(1), которая возвращает False (поэтому 1 отбрасывается), затем is_even(2) возвращает True (поэтому 2 остаётся), и так далее. Выживают только элементы, прошедшие проверку.
['hello', 'world', 'python'] ['hello', 'world', 'python'] [2, 5, 8]
Передача bool в filter() является изящным приёмом. Пустые строки, ноль, None и пустые списки в Python считаются «ложными» значениями: bool() возвращает для них False. Поэтому filter(bool, items) удаляет все ложные значения.
Объединяем map() и filter()
Настоящая сила проявляется, когда эти функции выстраивают в цепочку. Сначала filter отбирает элементы, потом map их преобразует, точно как посты на конвейере.
[87, 96, 72, 78, 93, 100, 65]
Поскольку и filter(), и map() ленивые, их можно объединять в цепочку без промежуточных списков. Данные проходят через конвейер без лишних затрат.
Вот пример ближе к практике, обработка списка цен на товары:
[10.39, 71.99, 36.0, 120.0]
reduce(): сводим все элементы к одному
reduce() принимает функцию и итерируемый объект и объединяет все элементы в одно значение. Это последний пост на конвейере: все детали собираются в одно изделие.
В отличие от map() и filter(), reduce() не является встроенной функцией. Её нужно импортировать из модуля functools.
Sum: 15 Product: 120
Вот как reduce() шаг за шагом считает сумму. Функция принимает два аргумента: накопленный на данный момент результат (a) и следующий элемент (b):
a=1, b=2 даёт 3a=3, b=3 даёт 6a=6, b=4 даёт 10a=10, b=5 даёт 15Longest word: hippopotamus Alice & Bob & Charlie
map/filter или списковые включения
В Python есть ещё один способ преобразовывать и фильтровать списки: списковые включения (list comprehensions). Код с ними часто читается легче, чем с map() и filter(). Давайте сравним.
numbers = [1, 2, 3, 4, 5, 6]
# Double even numbers
evens = filter(lambda x: x % 2 == 0, numbers)
result = list(map(lambda x: x * 2, evens))
print(result) # [4, 8, 12]numbers = [1, 2, 3, 4, 5, 6]
# Double even numbers
result = [x * 2 for x in numbers if x % 2 == 0]
print(result) # [4, 8, 12]Списковое включение выполняет и фильтрацию, и преобразование в одной понятной строке. Для простых преобразований включения в Python обычно оказываются лучшим выбором.
Так когда же стоит использовать map() и filter()? Они удобны, когда у вас уже есть именованная функция, которую нужно применить:
['Alice', 'Bob', 'Charlie'] ['Alice', 'Bob', 'Charlie']
Практические упражнения
Пора потренироваться. Упражнения начинаются с отдельных функций, а затем вы будете объединять их в конвейеры. Не забывайте оборачивать map() и filter() в list() при выводе.
С помощью map() и lambda возведите в квадрат каждое число списка [1, 2, 3, 4, 5].
Выведите результат в виде списка.
Ожидаемый вывод:
[1, 4, 9, 16, 25]С помощью filter() и lambda оставьте только слова длиннее 3 символов.
Дано: ["hi", "hello", "hey", "world", "ok", "python"]
Выведите результат в виде списка.
Ожидаемый вывод:
['hello', 'world', 'python']Прочитайте этот код и предскажите его вывод. Затем напишите инструкцию print(), которая выводит точно такой же результат.
numbers = [10, 25, 30, 45, 50]
result = list(map(lambda x: x // 10, filter(lambda x: x >= 30, numbers)))
print(result)Рассуждайте по шагам: что оставляет filter и что затем map делает с каждым элементом?
Этот код должен превратить список строк с температурами вида ["32F", "100F", "212F"] в целые числа в градусах Цельсия. Но в нём две ошибки.
Ожидаемый вывод:
[0, 38, 100]Формула: Цельсий = (Фаренгейт - 32) * 5 / 9, после чего результат округляется до ближайшего целого.
С помощью reduce() из модуля functools вычислите произведение всех чисел списка [2, 3, 4, 5].
Произведение равно 2 * 3 * 4 * 5 = 120.
Выведите результат.
Ожидаемый вывод:
120Дан список баллов учеников. Постройте конвейер, который:
1. Отбрасывает неудовлетворительные баллы (оставьте только баллы >= 60)
2. Повышает каждый проходной балл на 10 (используйте map)
3. Ограничивает баллы сверху значением 100 (используйте map вместе с min)
Дано: [45, 82, 55, 91, 67, 38, 73, 95]
Выведите итоговый список.
Ожидаемый вывод:
[92, 100, 77, 83, 100]Подсказка: чтобы балл не превышал 100, используйте min(score, 100).
Перепишите этот конвейер из map и filter в одно списковое включение, которое даёт такой же вывод.
Исходный код:
words = ["Hello", "WORLD", "Python", "CODE", "test"]
result = list(map(str.lower, filter(lambda w: len(w) > 4, words)))Напишите списковое включение с тем же результатом и выведите его.
Ожидаемый вывод:
['hello', 'world', 'python']Итоги
Вот что вы узнали о функциональных инструментах Python:
| Функция | Назначение | Возвращает |
|---|---|---|
map(fn, iterable) | Преобразует каждый элемент | Объект map (ленивый) |
filter(fn, iterable) | Оставляет элементы, для которых fn возвращает True | Объект filter (ленивый) |
reduce(fn, iterable) | Сводит все элементы к одному значению | Одно значение |
| Функция высшего порядка | Функция, которая принимает или возвращает функцию | Зависит от функции |
Практический вывод: для большинства задач преобразования и фильтрации в Python используйте списковые включения. Берите map() и filter(), когда у вас есть готовая именованная функция или нужны ленивые вычисления. Применяйте reduce() пореже: встроенные функции Python в большинстве случаев справляются лучше.
Что дальше?
Теперь у вас прочная база по функциям в Python. Дальше откройте урок [Обработка ошибок](/python/python-try-except): вы узнаете, как писать код, который аккуратно справляется с ошибками, а не падает.