Перейти к содержимому
Editor

map(), filter() и reduce() в Python: функции высшего порядка

Средний25 min7 упр.100 XP

Этот урок переведён с английского оригинала.Read in EnglishLeer en españolСообщить об ошибке в переводе

0/7 упр.

Представьте сборочный конвейер на заводе. С одного конца поступает сырьё. На первом посту каждой детали придают форму. На следующем отбраковывают дефектные детали. На последнем все годные детали собирают в одно готовое изделие.

В Python есть три функции, которые работают точно как такой конвейер: map() преобразует каждый элемент, filter() оставляет только нужные элементы, а reduce() объединяет всё в один результат. Вместе они позволяют обрабатывать коллекции данных аккуратно и понятно.

В этом уроке вы узнаете, как работают эти инструменты, когда их применять и когда списковое включение (list comprehension) окажется лучшим выбором. К концу урока вы будете строить конвейеры обработки данных, которыми гордился бы любой мастер цеха.

Что такое функции высшего порядка?

Функция высшего порядка принимает другую функцию в качестве аргумента или возвращает функцию как результат. Вот и всё.

Вы уже пользовались такой функцией, сами того не замечая. У встроенной функции sorted() есть параметр key, в который передаётся функция:

sorted() как функция высшего порядка
words = ["banana", "apple", "cherry"]
# sorted() is a higher-order function
# It takes len as an argument - a function!
sorted_words = sorted(words, key=len)
print(sorted_words)
Вывод
['apple', 'banana', 'cherry']

Здесь len передаётся в sorted() как аргумент. Функция sorted вызывает len для каждого элемента, чтобы определить порядок. Вы не вызывали len() сами, а только сообщили sorted, какую функцию использовать.

map(), filter() и reduce() работают так же. Вы передаёте им функцию и коллекцию, и они применяют эту функцию к элементам. Именно поэтому они считаются функциями высшего порядка.

map(): преобразуем каждый элемент

map() принимает функцию и итерируемый объект (например, список), применяет функцию к каждому элементу и возвращает преобразованные результаты. Это «пост обработки» на нашем конвейере.

Синтаксис такой: map(function, iterable). Функция возвращает объект map, который обычно преобразуют в список.

Удваиваем каждое число с помощью map()
numbers = [1, 2, 3, 4, 5]
# Double every number
def double(x):
return x * 2
result = list(map(double, numbers))
print(result)
Вывод
[2, 4, 6, 8, 10]

map(double, numbers) вызывает double(1), double(2), double(3) и так далее, собирая все результаты. Обёртка list() превращает объект map в обычный список, чтобы его можно было вывести.

Писать целую функцию только ради удвоения числа слишком громоздко, поэтому map() часто используют вместе с lambda, крошечной анонимной функцией:

map() с lambda и встроенными функциями
numbers = [1, 2, 3, 4, 5]
# Same thing, but with a lambda
result = list(map(lambda x: x * 2, numbers))
print(result)
# More examples
names = ["alice", "bob", "charlie"]
print(list(map(str.upper, names)))
prices = [10.5, 20.3, 5.99]
print(list(map(round, prices)))
Вывод
[2, 4, 6, 8, 10]
['ALICE', 'BOB', 'CHARLIE']
[10, 20, 6]

В map() можно передать любую функцию: вашу собственную, lambda или встроенную, например str.upper, round, len или int.

filter(): оставляем только нужное

filter() принимает функцию и итерируемый объект. Функция должна возвращать True или False для каждого элемента. Остаются только те элементы, для которых функция вернула True. Это контроль качества на конвейере: бракованные детали убирают.

Оставляем чётные числа с помощью filter()
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Keep only even numbers
def is_even(x):
return x % 2 == 0
result = list(filter(is_even, numbers))
print(result)
Вывод
[2, 4, 6, 8, 10]

filter(is_even, numbers) вызывает is_even(1), которая возвращает False (поэтому 1 отбрасывается), затем is_even(2) возвращает True (поэтому 2 остаётся), и так далее. Выживают только элементы, прошедшие проверку.

Ещё примеры с filter()
words = ["hello", "", "world", "", "python", ""]
# Remove empty strings - filter with bool
result = list(filter(bool, words))
print(result)
# Keep words longer than 4 characters
long_words = list(filter(lambda w: len(w) > 4, words))
print(long_words)
# Keep positive numbers
numbers = [-3, -1, 0, 2, 5, -4, 8]
positives = list(filter(lambda x: x > 0, numbers))
print(positives)
Вывод
['hello', 'world', 'python']
['hello', 'world', 'python']
[2, 5, 8]

Передача bool в filter() является изящным приёмом. Пустые строки, ноль, None и пустые списки в Python считаются «ложными» значениями: bool() возвращает для них False. Поэтому filter(bool, items) удаляет все ложные значения.

Объединяем map() и filter()

Настоящая сила проявляется, когда эти функции выстраивают в цепочку. Сначала filter отбирает элементы, потом map их преобразует, точно как посты на конвейере.

Цепочка из filter() и map()
scores = [45, 82, 91, 67, 55, 38, 73, 88, 95, 60]
# Step 1: Filter passing scores (>= 60)
passing = filter(lambda s: s >= 60, scores)
# Step 2: Add 5 bonus points to each passing score
boosted = map(lambda s: s + 5, passing)
# Convert to list and print
result = list(boosted)
print(result)
Вывод
[87, 96, 72, 78, 93, 100, 65]

Поскольку и filter(), и map() ленивые, их можно объединять в цепочку без промежуточных списков. Данные проходят через конвейер без лишних затрат.

Вот пример ближе к практике, обработка списка цен на товары:

Практический конвейер: сначала отбор, потом преобразование
prices = [12.99, 5.50, 89.99, 3.25, 45.00, 2.99, 150.00]
# Keep prices over $10, then apply 20% discount
result = list(
map(
lambda p: round(p * 0.8, 2),
filter(lambda p: p > 10, prices)
)
)
print(result)
Вывод
[10.39, 71.99, 36.0, 120.0]

reduce(): сводим все элементы к одному

reduce() принимает функцию и итерируемый объект и объединяет все элементы в одно значение. Это последний пост на конвейере: все детали собираются в одно изделие.

В отличие от map() и filter(), reduce() не является встроенной функцией. Её нужно импортировать из модуля functools.

Объединяем значения с помощью reduce()
from functools import reduce
numbers = [1, 2, 3, 4, 5]
# Sum all numbers: ((((1+2)+3)+4)+5) = 15
total = reduce(lambda a, b: a + b, numbers)
print(f"Sum: {total}")
# Multiply all numbers: ((((1*2)*3)*4)*5) = 120
product = reduce(lambda a, b: a * b, numbers)
print(f"Product: {product}")
Вывод
Sum: 15
Product: 120

Вот как reduce() шаг за шагом считает сумму. Функция принимает два аргумента: накопленный на данный момент результат (a) и следующий элемент (b):

  • Начинаем с первых двух элементов: a=1, b=2 даёт 3
  • Берём этот результат и следующий элемент: a=3, b=3 даёт 6
  • Продолжаем: a=6, b=4 даёт 10
  • В конце: a=10, b=5 даёт 15
  • Ещё примеры с reduce()
    from functools import reduce
    # Find the longest word
    words = ["cat", "elephant", "dog", "hippopotamus"]
    longest = reduce(lambda a, b: a if len(a) >= len(b) else b, words)
    print(f"Longest word: {longest}")
    # Join words with a separator
    result = reduce(lambda a, b: f"{a} & {b}", ["Alice", "Bob", "Charlie"])
    print(result)
    Вывод
    Longest word: hippopotamus
    Alice & Bob & Charlie

    map/filter или списковые включения

    В Python есть ещё один способ преобразовывать и фильтровать списки: списковые включения (list comprehensions). Код с ними часто читается легче, чем с map() и filter(). Давайте сравним.

    С помощью 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()? Они удобны, когда у вас уже есть именованная функция, которую нужно применить:

    map() особенно хорош с именованными функциями
    # When you have a named function, map() reads nicely
    names = ["alice", "bob", "charlie"]
    print(list(map(str.capitalize, names)))
    # Equivalent comprehension - a bit more verbose
    print([name.capitalize() for name in names])
    Вывод
    ['Alice', 'Bob', 'Charlie']
    ['Alice', 'Bob', 'Charlie']

    Практические упражнения

    Пора потренироваться. Упражнения начинаются с отдельных функций, а затем вы будете объединять их в конвейеры. Не забывайте оборачивать map() и filter() в list() при выводе.

    Упражнение 1: возведите каждое число в квадрат
    Напишите код

    С помощью map() и lambda возведите в квадрат каждое число списка [1, 2, 3, 4, 5].

    Выведите результат в виде списка.

    Ожидаемый вывод:

    [1, 4, 9, 16, 25]
    numbers = [1, 2, 3, 4, 5]
    # Use map() to square each number
    Упражнение 2: отфильтруйте короткие слова
    Напишите код

    С помощью filter() и lambda оставьте только слова длиннее 3 символов.

    Дано: ["hi", "hello", "hey", "world", "ok", "python"]

    Выведите результат в виде списка.

    Ожидаемый вывод:

    ['hello', 'world', 'python']
    words = ["hi", "hello", "hey", "world", "ok", "python"]
    # Use filter() to keep words longer than 3 characters
    Упражнение 3: предскажите результат конвейера
    Угадайте вывод

    Прочитайте этот код и предскажите его вывод. Затем напишите инструкцию print(), которая выводит точно такой же результат.

    numbers = [10, 25, 30, 45, 50]
    result = list(map(lambda x: x // 10, filter(lambda x: x >= 30, numbers)))
    print(result)

    Рассуждайте по шагам: что оставляет filter и что затем map делает с каждым элементом?

    # Print the exact output
    Упражнение 4: почините сломанный конвейер
    Исправьте ошибку

    Этот код должен превратить список строк с температурами вида ["32F", "100F", "212F"] в целые числа в градусах Цельсия. Но в нём две ошибки.

    Ожидаемый вывод:

    [0, 38, 100]

    Формула: Цельсий = (Фаренгейт - 32) * 5 / 9, после чего результат округляется до ближайшего целого.

    temps = ["32F", "100F", "212F"]
    # Bug 1: Need to extract the number from the string
    # Bug 2: Formula or conversion issue
    result = list(map(lambda t: round((t - 32) * 5 / 9), temps))
    print(result)
    Упражнение 5: найдите произведение с помощью reduce()
    Напишите код

    С помощью reduce() из модуля functools вычислите произведение всех чисел списка [2, 3, 4, 5].

    Произведение равно 2 * 3 * 4 * 5 = 120.

    Выведите результат.

    Ожидаемый вывод:

    120
    from functools import reduce
    numbers = [2, 3, 4, 5]
    # Use reduce() to multiply all numbers together
    Упражнение 6: постройте конвейер обработки данных
    Напишите код

    Дан список баллов учеников. Постройте конвейер, который:

    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).

    scores = [45, 82, 55, 91, 67, 38, 73, 95]
    # Step 1: Filter passing scores (>= 60)
    # Step 2: Add 10 bonus points
    # Step 3: Cap at 100
    Упражнение 7: перепишите код через списковое включение
    Улучшите код

    Перепишите этот конвейер из map и filter в одно списковое включение, которое даёт такой же вывод.

    Исходный код:

    words = ["Hello", "WORLD", "Python", "CODE", "test"]
    result = list(map(str.lower, filter(lambda w: len(w) > 4, words)))

    Напишите списковое включение с тем же результатом и выведите его.

    Ожидаемый вывод:

    ['hello', 'world', 'python']
    words = ["Hello", "WORLD", "Python", "CODE", "test"]
    # Refactor to a list comprehension
    result = [] # Replace with your comprehension
    print(result)

    Итоги

    Вот что вы узнали о функциональных инструментах Python:

    ФункцияНазначениеВозвращает
    map(fn, iterable)Преобразует каждый элементОбъект map (ленивый)
    filter(fn, iterable)Оставляет элементы, для которых fn возвращает TrueОбъект filter (ленивый)
    reduce(fn, iterable)Сводит все элементы к одному значениюОдно значение
    Функция высшего порядкаФункция, которая принимает или возвращает функциюЗависит от функции

    Практический вывод: для большинства задач преобразования и фильтрации в Python используйте списковые включения. Берите map() и filter(), когда у вас есть готовая именованная функция или нужны ленивые вычисления. Применяйте reduce() пореже: встроенные функции Python в большинстве случаев справляются лучше.

    Что дальше?

    Теперь у вас прочная база по функциям в Python. Дальше откройте урок [Обработка ошибок](/python/python-try-except): вы узнаете, как писать код, который аккуратно справляется с ошибками, а не падает.

    Похожие уроки

    Урок был полезен?