Функция map() в Python
hard

Функция map() в Python

Отлично работает, применяется и приносит пользу

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

Функциональное программирование

Перед тем как переходить к map(), вспомним про подходы к программированию.

Программирование по принципам и идеям делится на несколько основных подходов-парадигм. Есть две основные — императивная и декларативная, и каждая из них включает много других.

Функциональное программирование не означает фокус на функциях. В центре этого подхода лежат более широкие идеи:

  • Данные не изменяются после создания.
  • Программы строятся из функций как из блоков. Мы описываем не чёткие команды, а правила взаимодействия этих блоков.
  • Вычисления проводятся только в тот момент, когда действительно нужен результат. Это называется «ленивые вычисления».
  • То, что происходит в функциях, не изменяет состояние программы и не зависит от условий. Это означает, что одни и те же данные на входе всегда дадут одни и те же значения на выходе.
  • Функции могут принимать другие аргументы в качестве функций.

Некоторые языки программирования построены так, что писать код по-другому и не применять эти правила невозможно. В других идеи функционального программирования приходится встраивать в свой код.

Главные мысли во всём этом — программа должна производить чистые вычисления, которые не затрагивают её саму и внешние системы. А ещё — логика строится на основе композиции из функций, которые обеспечивают такие чистые вычисления.

Если бы мы говорили только про функции, то основной идеей было бы просто использование функций. Но в парадигме полагается использовать не любые функции, а те, которые подходят под всю концепцию.

Какие функции должны быть в функциональном программировании

Функциональное программирование основано на использовании чистых функций, которые:

  • всегда возвращают одинаковый результат для одного и того же набора входных данных;
  • не изменяют состояние программы и не взаимодействуют с внешними системами — файлы, базы данных, устройства ввода/вывода;
  • можно заменить на результат выполнения, и это ничего не изменит — например, функцию возведения в квадрат можно заменить на квадрат нужного числа.

Одна из таких чистых функций — map() в Python.

Введение в функцию map()

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

Map() — функция высшего порядка. Это значит, что она принимает другие функции в качестве аргумента или возвращает их или всё сразу.

👉 Важно то, что map() в Питоне не изменяет объекты, с которыми работает, а создаёт новые. Это отвечает идее чистого функционального программирования, когда вычисления не влияют на остальной код.

Обычно для работы с итерируемыми объектами в Python используют циклы for, но у map() есть преимущества:

  • Скорость. Хотя map используется в Python, она написана на C и оптимизирована, поэтому может работать быстрее цикла.
  • Экономия ресурсов. При использовании цикла в памяти хранится весь список, а map использует ленивые вычисления: всё подсчитывается по запросу, и в памяти хранится только один элемент за раз.

Map(), filter() и reduce()

Изначально функцию map() Python не включал, но в 1993 году добавили её и ещё две других: map(), filter() и reduce().

Эти три функции позволяют реализовать три основные техники при написании функционального кода:

  • Mapping (отображение). Применение функции к каждому элементу итерируемого объекта. В результате mapping появляется новый объект, а старый остаётся неизменным. Например, создание списка квадратов исходных чисел.
  • Filtering (фильтрация). Исключение элементов, которые не соответствуют предикату — условию. Например, мы хотим получить список квадратов из чисел, которые хранятся в исходном списке. Но нам нужны не все числа, а только чётные. Условие проверит первоначальный список, вернёт False для всех нечётных чисел, и при создании нового объекта они будут исключены.
  • Reducing (редукция). Это объединение всех элементов в одно значение. Например, если нужно найти сумму, произведение или максимальное число.

Сначала — про mapping и про map().

Синтаксис функции map()

Разберём, как работает map.

Основной синтаксис выглядит как ключевое слово map() с двумя значениями в скобках: функцией и итерируемым объектом.

Вот пример: у нас есть список элементов из первых 10 букв кириллического алфавита:

rus_letters = ['а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и']

Мы хотим сделать новый список, добавив к каждой букве цифровой индекс. Например, 'а1', 'б1', 'в1'. Для этого мы создаём новую переменную result, потому что при функциональном программировании нельзя изменять уже существующие данные.

После объявления переменной мы говорим компьютеру:

  • создай список list();
  • этот список нужно создать с помощью функции map();
  • функцией для map() будет безымянная лямбда-функция, которая прибавляет '1' к каждому элементу x;
  • элементы x берутся из списка с 10 буквами.

Так выглядит команда целиком:

use = list(map(lambda x: x + '1', ['а', 'б', 'в', 'г', 'д', 'е', 'ё', 'ж', 'з', 'и']))

Теперь, если вывести use на экран командой print, мы увидим новый список:

['а1', 'б1', 'в1', 'г1', 'д1', 'е1', 'ё1', 'ж1', 'з1', 'и1']

Использование функции map() с различными видами функций

В качестве функции map() может принимать любую Python-функцию, которая принимает такое количество аргументов, сколько их содержится в итерируемом объекте.

Вот примеры того, как это может выглядеть.

Использование map() с лямбда-функциями. У обычных классических функций в Python есть имя, тело и результат. Лямбда-функции выглядят как одна строка. Вместо имени у них есть только ключевое слово lambda.

Преобразование списка чисел в квадраты этих чисел этим путём будет выглядеть так:

# создаём список
numbers = [1, 2, 3, 4, 5]

# создаём новый список с использованием map
squares = list(map(lambda x: x**2, numbers))

# выводим результат на экран
print(squares)

В консоли вывода получим:

[1, 4, 9, 16, 25]

Использование map() со встроенными функциями. Иногда можно не придумывать свой алгоритм, а взять готовый. Например, вместо лямбда-функций использовать встроенные функции Python: str, len, abs.

Пример — делаем из списка с целыми числами список из строк. Для этого применяем функцию str на каждом элементе и создаём из числа строку:

# создаём список
numbers = [1, 2, 3, 4, 5]

# создаём новый список, используя map
string_numbers = list(map(str, numbers))

# выводим результат на экран
print(string_numbers)

Результат:

['1', '2', '3', '4', '5']

Использование map() с несколькими итерациями. Map() умеет принимать сразу несколько итерируемых объектов. Тогда функция-аргумент должна принимать столько же аргументов, сколько объектов передано.

Можно передать два списка и сложить элементы под одними и теми же номерами, или индексами:

# создаём первый список
list1 = [1, 2, 3]

# создаём второй список
list2 = [4, 5, 6]

# создаём список из суммы элементов
# двух списков с одинаковыми индексами
sums = list(map(lambda x, y: x + y, list1, list2))

# выводим результат на экран
print(sums)

Запускаем код:

[5, 7, 9]

Примеры использования функции map()

Разберём работу map() ещё на нескольких примерах.

Преобразование строковых итераций. Map() можно использовать для преобразования списка строк, например изменения регистра. 

Для этого применим встроенную функцию str() к списку строковых объектов:

# создаём список строк
words = ['hello', 'world', 'python']

# создаём новый список и приводим
# все слова к верхнему регистру
uppercase_words = list(map(str.upper, words))

# выводим результат
print(uppercase_words)

В консоли получим:

['HELLO', 'WORLD', 'PYTHON']

Преобразование числовых итераций. Списки и другие изменяемые коллекции можно использовать, чтобы на их основе создать новые.

Пример. Создаём список из чисел и применяем к каждому элементу математическую операцию: увеличиваем все числа на 10.

# создаём список из целых чисел
numbers = [1, 2, 3, 4, 5]

# создаём новый список с помощью map,
# увеличивая каждое число исходного списка на 10
increased_numbers = list(map(lambda x: x + 10, numbers))

# выводим результат на экран
print(increased_numbers)

В новом списке все элементы — сумма изначальных элементов и 10:

[11, 12, 13, 14, 15]

Удаление пробелов и знаков препинания. Строки можно не только преобразовывать, но и очищать. Например, удалять ненужные пробелы и знаки препинания.

Удаляем пробелы из строк, применяя к элементам встроенный метод str.strip:

# создаём список из объектов-строк с пробелами
phrases = ['  hello ', ' world ', ' python  ']

# создаём новый список и с помощью map проходим по каждому
# элементу методом strip(), который удаляет пробелы
cleaned_phrases = list(map(str.strip, phrases))

# выводим результат на экран
print(cleaned_phrases)

Получаем очищенную строку:

['hello', 'world', 'python']

Другой пример: импортируем модуль string и получаем доступ к методу punctuation, который знает все стандартные знаки препинания. Если этот метод передать в метод strip, который очищает строки, можно очистить коллекции строк:

# импортируем модуль для работы со сроками
import string

# создаём список из объектов-строк со знаками препинания
phrases = ['hello!', 'world,', 'python.']

# создаём новый список и с помощью map и функции lambda удаляем все знаки препинания
object = list(map(lambda x: x.strip(string.punctuation), phrases))

# выводим результат на экран
print(object)

Проверяем результат:

['hello', 'world', 'python']

Конвертация температур. Map можно настроить на более интересные задачи. Например, задать формулу, по которой переводятся градусы из шкалы по Цельсию в шкалу Фаренгейта.

Для этого формулу нужно записать в лямбда-функцию:

# создаём список из целых чисел
celsius_temps = [0, 20, 30, 40]

# создаём новый список и с помощью lambda-функции
# применяем к каждому элементу формулу
# преобразования градусов по Цельсию в Фаренгейты
fahrenheit_temps = list(map(lambda c: c * 9/5 + 32, celsius_temps))

# выводим результат на экран
print(fahrenheit_temps)

Смотрим, что получилось:

[32.0, 68.0, 86.0, 104.0]

Недостатки функции map()

Map() — мощный инструмент, который можно настроить для разных ситуаций. Но минусы у этой функции тоже есть:

  • В сочетании с lambda-функциями map() становится довольно сложной для понимания. Чем длиннее lambda-функция, тем сложнее разобраться в итоговом выражении.
  • Map() неудобно использовать при итерации вложенных объектов. Например, при работе со списками внутри списков проще будет воспользоваться другими инструментами языка.
  • В map() нет встроенного механизма для обработки ошибок в функции. Если внутри lambda-функции что-то сломается, разобраться может быть сложно.

Альтернативы функции map()

В Python есть и другие технологии со схожими возможностями: списковые включения и генераторные выражения.

Использование списковых включений, или генераторов списков (list comprehensions), — один из наиболее популярных способов преобразования данных в Python. Они понятные для чтения и гибкие.

Вот два примера, каждый из которых создаёт новый список, увеличивая исходный на 10:

# исходный список
item = [1, 2, 3]

# создаём новый список с функцией map()
result = list(map(lambda x: x + 10, item))

# создаём новый список со списочным включением
result = [x + 10 for x in numbers]

Видно, что генератор списков выглядит более читаемым: в нём меньше операторов, действий и аргументов. 

Ещё код list comprehensions позволяет включать условия фильтрации, а в map() такой возможности нет. Например, мы можем создать новый список из исходного, используя только чётные элементы:

result = [x + 10 for x in numbers if x % 2 == 0]

Использование генераторных выражений. Генераторные выражения похожи на списочные включения, но возвращают итератор вместо списка.

Что нужно вспомнить про итераторы и итерирование:

  • Итератор (iterator) — это объект, который позволяет проходить по элементам коллекции и возвращать по одному элементу. 
  • Итерируемый объект (iterable) — объект, который возвращает эти элементы. Из него можно получить итератор.
  • Итерировать — повторять какую-то операцию несколько раз. Например, перебирать буквы в слове.

Это экономит память, потому что избавляет от необходимости хранить всю последовательность в памяти целиком.

Когда мы используем циклы и перебираем все элементы списка или другой коллекции, внутри этой системы используется итератор.

Как это работает:

  • Мы создаём список или другую коллекцию данных.
  • Используем генераторное выражение на исходной коллекции и получаем итератор, внутри которого — новые значения.
  • Итератор можно использовать разными способами — преобразовать в коллекцию или передать в другие функции, которые умеют работать с итераторами.

# создаём список
numbers = [1, 2, 3]

# используем генераторное выражение,
# которое возвращает итератор
iterables = (x + 10 for x in numbers)

Внутри получившегося итератора находятся целые числа: 11, 12 и 13. По ним можно пройтись циклом или переделать в итерируемый объект-коллекцию, но сейчас это именно итератор, а не итерируемый объект.

Сравнение map() с другими функциями

Map() — одна из трёх функций, которые часто составляют основу функциональной программы. Если сравнить их, то получатся примерно такие ключевые наблюдения:

  • map() преобразует элементы;
  • filter() отбирает элементы;
  • reduce() сводит коллекцию к одному значению, комбинируя элементы.

Сравнение с filter(). Map() берёт готовые элементы и на их основе делает новый итерируемый объект. А filter() используется для отбора элементов, которые удовлетворяют определённому условию (предикату).

Синтаксис map() и filter() похож. Так будет работать отбор чётных чисел из существующего списка:

# создаём новый список
numbers = [1, 2, 3, 4, 5]
# создаём итератор на основе списка
result = list(filter(lambda x: x % 2 == 0, numbers))

Сравнение с reduce(). Эта функция — часть модуля functools. Она последовательно применяет функцию, чтобы свернуть итерируемый объект до одного значения.

Объясним на примере:

# импортируем reduce из модуля functools
from functools import reduce

# создаём список
numbers = [1, 2, 3, 4]

# суммируем элементы списка
result = reduce(lambda x, y: x + y, numbers)

# выводим результат на экран
print(result)

Результатом работы reduce() будет сумма всех элементов списка — 10. Для этой цели можно было использовать другую встроенную функцию — sum(). Она тоже отвечает требованиям функционального программирования, но не умеет делать остальных вещей, которые можно делать с reduce, например умножения чисел или объединения строк.

Обложка:

Алексей Сухов

Корректор:

Ирина Михеева

Вёрстка:

Кирилл Климентьев

Соцсети:

Юлия Зубарева

Получите ИТ-профессию
В «Яндекс Практикуме» можно стать разработчиком, тестировщиком, аналитиком и менеджером цифровых продуктов. Первая часть обучения всегда бесплатная, чтобы попробовать и найти то, что вам по душе. Дальше — программы трудоустройства.
Вам может быть интересно
hard
[anycomment]
Exit mobile version