Рассмотрим следующую задачу. Возьмём словарь пользователей и извлечём из него имена всех пользователей:
users = [
{ 'name': 'Igor', 'age': 19 },
{ 'name': 'Danil', 'age': 1 },
{ 'name': 'Vovan', 'age': 4 },
{ 'name': 'Matvey', 'age': 16 },
]
result = []
for user in users:
result.append(user['name'])
print(result) # => ['Igor', 'Danil', 'Vovan', 'Matvey']
Здесь мы видим обычную агрегацию с использованием цикла for...in. А что, если нам понадобится извлечь возраст? Повторяем:
result = []
for user in users:
result.append(user['age'])
print(result) # => [19, 1, 4, 16]
В примерах выше легко увидеть закономерность. Выполняется один и тот же проход по циклу, и результат собирается в список result
. Единственное, что меняется — значение, которое мы извлекаем из элементов исходного словаря.
Операция, которую мы выполняли в обеих ситуациях, называется отображением (mapping). В коде мы взяли исходную коллекцию и отобразили ее в другую коллекцию, попутно выполнив необходимые преобразования над каждым элементом. Важно, что размер получившейся коллекции равен размеру исходной.
Задача отображать данные в реальном коде встречается буквально на каждом шагу. Это настолько важная операция, что для нее создана специальная функция высшего порядка map():
names = map(lambda user: user['name'], users)
list(names) # => <map object at 0x71de31ee0250>
for name in names:
print(name)
# => Igor
# => Danil
# => Vovan
# => Matvey
Функция map()
принимает первым параметром функцию, а последующими - коллекции (их может быть несколько). Дальше, внутри себя, map()
перебирает элементы переданной коллекции и для каждого элемента вызывает переданную функцию. Возвращает map()
уже знакомый вам итератор.
Этот момент очень важен, map()
использует ленивые вычисления, и значит функция, переданная в map()
, будет применяться к каждому элементу только по запросу.
Вместо перебора в цикле, существует более простой способ извлечь все элементы из итератора - применить конструктор list()
names = map(lambda user: user['name'], users)
list(names) # ['Igor', 'Danil', 'Vovan', 'Matvey']
ages = map(lambda user: user['age'], users)
list(ages) # [19, 1, 4, 16]
# или зададим колбек отдельно
callback = lambda user: user['age']
list(map(callback, users)) # [19, 1, 4, 16]
Сравните решение задачи получения списка имен через цикл и с помощью map()
. Последний имеет довольно много преимуществ. Во-первых, код с ним значительно короче. Во-вторых, от нас скрыта повторяющаяся логика перебора. Больше не нужно явно определять цикл и выполнять руками все те операции, которые можно не выполнять. Функция map()
позволяет сосредоточиться на сути происходящего, скрывая ненужные детали (проход по циклу).
Типичный пример, который любят приводить в документации к функции map()
разных языков программирования — применение некоторой арифметической операции к каждому элементу коллекции:
numbers = [5, 2, 3]
new_numbers = map(lambda number: number ** 2, numbers)
list(new_numbers) # => [25, 4, 9]
new_numbers_2 = map(lambda number: number + 3, numbers)
list(new_numbers_2) # => [8, 5, 6]
Пример выглядит искусственно, но хорошо отражает суть операции.
Реализация
Напишем свою собственную функцию my_map()
, работающую аналогично встроенной функции map()
:
def my_map(callback, collection):
for item in collection:
# Вызов переданного колбека на каждом элементе коллекции
new_item = callback(item)
# Сделаем нашу функцию тоже ленивой с помощью yield
yield new_item
numbers = [5, 2, 3]
new_numbers = my_map(lambda number: number ** 2, numbers)
list(new_numbers) # [25, 4, 9]
Главное отличие функции my_map()
(и встроенной map()
) от ручного обхода коллекции заключается в том, что функция my_map()
не знает, что нужно сделать с каждым элементом коллекции. Поэтому она принимает функцию-колбек, которую вызывает для каждого элемента исходной коллекции. Чем будет этот результат — функция my_map()
не знает, и ей этого знать не нужно. Ответственность за обработку лежит на пользователях.
Дополнительные материалы
Остались вопросы? Задайте их в разделе «Обсуждение»
Вам ответят команда поддержки Хекслета или другие студенты
Для полного доступа к курсу нужен базовый план
Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.