Перейти к содержанию

412 уроков, 25 библиотек и челлендж «Что выведет код?» — бесплатно, код прямо в браузере

Начать обучение
Урок 7 из 20 Начальный 15 мин 100 XP

Функция map в Python: обработка списка

map применяет функцию к каждому элементу списка одной строкой. Разбираем ленивый итератор, обёртку list() и правило выбора между map и циклом.

Редакция Питоники

Применить правило к каждому элементу списка — самая частая работа с данными: цены умножить на курс, слова привести к верхнему регистру, числа возвести в квадрат. Вы умеете писать это циклом с аккумулятором из урока про счётчики и аккумуляторы — и повторяете тот же каркас каждый раз. Встроенная функция map делает эту работу одной строкой: применяет ваше правило к каждому элементу и отдаёт результаты списком.

Что делает функция map в Python: коротко

map применяет переданную функцию к каждому элементу набора и отдаёт итератор: map(правило, список). Результаты собираются списком через list(map(...)). Функцию-правило передают именем без скобок или короткой lambda прямо на месте вызова. Печатать сам map нельзя — получится служебная запись с адресом памяти.

Знакомый цикл

Задача: удвоить каждую цену. Решение циклом занимает четыре строки каркаса:

цикл с append
prices = [100, 250, 80]
doubled = []
for price in prices:
    doubled.append(price * 2)

print(doubled)
Вывод
[200, 500, 160]

Каркас один и тот же для любой задачи: пустой список, цикл, append результата, печать. Меняется только правило — price * 2. Именно это правило map и принимает на вход.

map: правило и список

map вызывает правило для каждого элемента и собирает результаты:

map вместо цикла
prices = [100, 250, 80]

def double(n):
    return n * 2

doubled = list(map(double, prices))
print(doubled)
Вывод
[200, 500, 160]

Разбираем вызов map(double, prices): первый аргумент — функция-правило, переданная именем без скобок, как в уроке про lambda: map сам будет её вызывать; второй — список. map применяет функцию к каждому элементу списка, а list собирает результаты в новый список. Double отработала для 100, потом 250, потом 80 — итог совпал с циклом, четыре строки свёрнулись в одну.

map ленивый: сначала итератор

Важная деталь: map не считает сразу. Он возвращает итератор — заготовку, которая знает правило и список, но применяет их только когда результаты запрошены. Запрос — это обёртка list():

заготовка и сборка
numbers = [1, 2, 3]

def square(n):
    return n * n

prepared = map(square, numbers)
collected = list(prepared)
print(collected)
Вывод
[1, 4, 9]

В prepared лежит не список квадратов, а итератор: сама по себе эта строка ничего не вычислила. Всё посчиталось в момент list(prepared). Поэтому правило курса: результат map сразу оборачивайте в list — тогда у вас в руках обычный список, с которым можно печатать, считать и сохранять.

Ленивость видна и в другом свойстве: итератор одноразовый. Он выдаёт элементы один раз — повторная сборка вернёт пустой список:

итератор выдаёт элементы один раз
numbers = [1, 2, 3]

def square(n):
    return n * n

squares = map(square, numbers)
print(list(squares))
print(list(squares))
Вывод
[1, 4, 9]
[]

Первый list(squares) вытянул из итератора все три квадрата. Второй нашёл его пустым — элементы уже выданы, заново map их не считает. Если результаты нужны дважды — соберите список один раз и сохраните в переменную.

ошибка: печать map напрямую
# Так не делают: печать итератора вместо его сборки
prices = [100, 250]
print(map(lambda n: n * 2, prices))
В выводе окажется не список, а служебная запись вида <map object at 0x7f...> с адресом памяти — он свой у каждой машины, поэтому результаты нужно собирать: print(list(map(...))).
ошибка: индексы у итератора
# Так не запускается: у map нет индексов
prices = [100, 250]
doubled = map(lambda n: n * 2, prices)
print(doubled[0])
Python ответит TypeError: 'map' object is not subscriptable. Итератор не поддерживает обращение по индексу — сначала соберите список: list(doubled)[0].

Правило может быть любой функцией

Первым аргументом map принимает любую функцию — в том числе встроенную. Правило «возьми длину» уже написано за вас:

встроенная len как правило
words = ['кот', 'слон', 'жираф']
lengths = list(map(len, words))
print(lengths)
Вывод
[3, 4, 5]

len передана именем, без скобок: map сам вызовет её для каждого слова. А когда готовой функции нет, правило пишется lambda прямо на месте вызова — короткое, одноразовое:

lambda как правило
rubles = [100, 250, 80]
euros = list(map(lambda r: r / 2, rubles))
print(euros)
Вывод
[50.0, 125.0, 40.0]

Связка map + lambda — рабочая лошадка обработки списков: правило объявляется и используется в одной строке, отдельная def для разового пересчёта не нужна. Для повторно используемых правил — например, конвертации с курсом из урока про return — честнее именованная функция:

Собираем: конвертация цен

именованная функция как правило
def ruble_to_euro(rubles):
    return rubles / 100

prices = [1000, 2500, 800]
euros = list(map(ruble_to_euro, prices))
print('Цены в евро:', euros)
print('Дороже всего:', max(euros))
Вывод
Цены в евро: [10.0, 25.0, 8.0]
Дороже всего: 25.0

Функция-правило возвращает результат, map разворачивает её по списку, list собирает, max находит максимум — детали из разных уроков сложились в одну программу из четырёх строк.

map или цикл: как выбирать

mapЦикл for
Одно простое правило для каждого элементаДа — одна строкаМожно, но длиннее
Условия, пропуски, несколько строк на элементНе поместится — lambda одновыраженческаяДа — тело цикла свободное
Счётчики и аккумуляторыНет — map не считает итогиДа — total += ... в теле
ЧитаемостьПривыкшему глазу — корочеПонятна с первых уроков

map покрывает правило «применить ко всем». Частая соседняя задача — «оставить только подходящие»: положительные цены, слова короче пяти букв. Для неё у Python есть функция filter, близнец map — им посвящён следующий урок. А после filter правило встретится снова: sorted с key сортирует список по вашей lambda.

map с двумя списками

У map есть версия, о которой в учебниках пишут через запятую и потому её пропускают: первым аргументом можно передать несколько списков, и правило получит по одному элементу из каждого. Так считают, например, стоимость позиций заказа: цена из одного списка и количество из другого.

правило получает элементы из двух списков
def line_total(price, count):
    return price * count

prices = [100, 250, 80]
counts = [2, 1, 5]

print(list(map(line_total, prices, counts)))
Вывод
[200, 250, 400]

Правило получило три пары: (100, 2), (250, 1), (80, 5) — и вернуло три результата. Длины списков при этом не обязаны совпадать: map остановится на самом коротком и лишние элементы просто не попадут в результат.

списки разной длины
print(list(map(lambda a, b: a + b, [1, 2, 3], [10, 20])))
Вывод
[11, 22]

Тройка из первого списка осталась без пары — и потерялась молча, без ошибки. Это стоит держать в голове: map с двумя списками не предупреждает о расхождении длин. Если данные приходят из разных источников, проверьте длины до вызова, а не после.

Ленивость на практике: считаем, не сохраняя

Главная выгода ленивого map в том, что он не хранит результаты. Он отдаёт их по одному и забывает — а значит, можно считать по большому набору, не создавая для этого список в памяти.

сумма без промежуточного списка
numbers = range(1000)
total = sum(map(lambda n: n * 2, numbers))
print(total)
Вывод
999000

Сумма посчитана по тысяче элементов, но ни один из них не пережил следующий. Замените sum на list(...) — и в памяти появится список из тысячи элементов. На диапазоне в миллион это десятки мегабайт, которые просто не нужны ради одного числа. Мы держим правило: если результат нужен целиком — оборачиваем в list, если нужен один показатель — считаем на лету.

map — это короткая запись генераторного выражения

Под капотом map — тот же генератор, который можно написать руками. Сравните две строки:

одно и то же разными словами
def double(n):
    return n * 2

nums = [1, 2, 3]

print(list(map(double, nums)))
print([double(n) for n in nums])
Вывод
[2, 4, 6]
[2, 4, 6]

Результаты одинаковые, отличается только форма. Разница в читаемости: когда правило сложное, его удобнее написать именованной функцией и передать в map, — тогда в скобках остаётся короткое map(double, nums). Когда правило простое, генераторное выражение читается ничуть не хуже, а иногда понятнее: видно сразу, что это перебор.

map по словарю

Словарь тоже можно разобрать на элементы и прогнать через map. Метод items() даёт пары «ключ — значение», и правило получает каждую пару целиком.

правило получает пары
def line(item):
    name, price = item
    return f'{name} — {price} руб.'

prices = {'Хлеб': 100, 'Молоко': 250, 'Сыр': 80}

print(list(map(line, prices.items())))
Вывод
['Хлеб — 100 руб.', 'Молоко — 250 руб.', 'Сыр — 80 руб.']

Порядок строк совпадает с порядком добавления ключей — словарь в Python сохраняет порядок, поэтому вывод предсказуем. Та же схема работает для keys() и values(), если правилу нужен только один элемент пары.

Правилом может быть и метод

У встроенных методов та же сигнатура, что у обычной функции: одно значение на вход, одно на выход. Поэтому их тоже можно передавать в map как правило — отдельно объявлять функцию не нужно.

методы строк как правила
words = ['  кот ', ' собака ', ' ёж']

clean = list(map(str.strip, words))
print(clean)
print(list(map(str.title, clean)))
Вывод
['кот', 'собака', 'ёж']
['Кот', 'Собака', 'Ёж']

Здесь метод передали именем — str.strip, без скобок, — ровно так же, как любую функцию. Если понадобится правило посложнее, пишем def и передаём её тем же способом.

Что выведет код?

Сначала предскажи ответ в голове — это главный навык программиста.

def f(n):
    return n + 1

print(list(map(f, [1, 2])))
nums = [1, 2]
m = map(lambda n: n * n, nums)
print(list(m))
print(list(m))
print(list(map(len, ['ок', 'дом'])))
Проверь себя
0 / 9

1. Что делает map(f, xs)?

2. Что возвращает сам вызов map(f, xs) без обёртки list()?

3. Почему нельзя печатать map напрямую — print(map(f, xs))?

4. Что напечатает print(list(map(lambda n: n + 1, [1, 2])))?

5. m = map(f, xs). Что вернёт второй вызов list(m) после первого?

6. Может ли правилом для map быть встроенная функция?

7. Что напечатает print(list(map(lambda a, b: a + b, [1, 2, 3], [10, 20])))?

8. Чем map отличается от генераторного выражения?

9. Как проверить, что map действительно ленивый?

Карточки терминов
Запомнено: 0 / 8
Практика

Дан список слов words = ['кот', 'собака', 'ёж']. Напиши функцию censor(word), которая возвращает слово с первой буквой и звёздочками вместо остальных: первая буква плюс '*' * (len(word) - 1). Собери через list(map(censor, words)) список hidden и напечатай его.

practice.py
Вопросы и ответы по уроку

Что делает функция map в Python?

Применяет переданную функцию к каждому элементу списка: map(double, prices) вызовет double для каждой цены. Результаты собираются списком через list(...): map возвращает ленивый итератор, а не готовый список.

Урок 8: filter

Почему print(map(...)) показывает <map object at 0x...>?

map ленивый: без запроса он ничего не вычисляет и печатается как служебная запись итератора с адресом памяти. Результат сразу оборачивают в list: print(list(map(f, xs))) напечатает список результатов.

Как использовать map с lambda?

Передать lambda первым аргументом: list(map(lambda r: r / 2, rubles)) — правило объявляется прямо на месте вызова. Для повторно используемых правил честнее именованная def, переданная именем без скобок.

map быстрее цикла for?

Смысл map не в скорости, а в краткости: одно простое правило на каждый элемент пишется одной строкой. Как только появляются условия, счётчики или несколько действий на элемент — обычный цикл читабельнее и свободнее.

Можно ли применить map сразу к двум спискам?

Да: map(line_total, prices, counts) вызовет правило с парой элементов — по одному из каждого списка. Так считают стоимость позиций заказа: цена из одного списка, количество из другого.

Что будет, если списки для map разной длины?

map возьмёт кратчайший список и лишние элементы отбросит — без ошибки и без предупреждения. Перед вызовом проверяйте длины: len(a) == len(b). Иначе потерянные данные обнаружатся позже, уже в готовом отчёте.

Как map экономит память?

Никаких промежуточных списков: sum(map(f, xs)) считает, отдавая результаты по одному. На диапазоне в миллион элементов list(map(...)) занял бы десятки мегабайт, а сумма — нисколько. Обёртка list(...) нужна только когда результат требуется целиком.

Можно ли использовать map со словарём?

Да, через items(): map(line, prices.items()) передаёт правилу каждую пару «ключ — значение» целиком. Порядок строк в результате совпадает с порядком добавления ключей — словарь в Python сохраняет порядок.

Понравился урок? Сошлитесь на него

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

Скопируйте готовую ссылку в формате HTML, Markdown или чистый адрес и вставьте в статью на Habr, VC, Telegram-канал или свой блог — так о проекте узнают новые читатели.

TelegramVK

Что дальше

Осталось в разделе: 20 из 20. Прогресс хранится в вашем браузере.

Похожие уроки по темам

Подобраны автоматически по пересечению тем и ключевых слов.