Python map(): обработка массивов без циклов
Перевод: Leodanis Pozo Ramos — Python’s map(): Processing Iterables Without a Loop
Table of Contents
- Функциональный стиль на Python
- Начало работы с map в Python
- Что такое map()
- Использование map() с различными видами функций
- Обработка множественных итераций с помощью map()
- Преобразование итераций строк с помощью map()
- Использование методов str
- Удаление знаков препинания
- Реализация алгоритма шифрования Цезаря
- Преобразование итераций чисел с помощью map
- Использование математических операций
- Преобразование температур
- Преобразование строк в числа
- Комбинирование map() с другими функциональными инструментами
- map() и filter()
- map() и reduce()
- Обработка итераций на основе кортежей с помощью starmap()
- Кодирование в питоническом стиле: замена map()
- Использование списковое включение
- Использование выражений генератора
- Заключение
Python map()
— это встроенная функция, которая позволяет обрабатывать и преобразовывать все элементы в итерируемом объекте без использования явного цикла for, метода, широко известного как сопоставление (mapping). map() полезен, когда вам нужно применить функцию преобразования к каждому элементу в коллекции или в массиве и преобразовать их в новый массив.
map() — один из инструментов, поддерживающих стиль функционального программирования в Python.
В этой статье вы узнаете:
- Как работает Python map()
- Как преобразовать различные типы массивов Python с помощью map()
- Как объединить map() с другими функциональными инструментами для выполнения более сложных преобразований
- Какие инструменты вы можете использовать, чтобы заменить map() и сделать свой код более Pythonic
Обладая этими знаниями, вы сможете эффективно использовать map() в своих программах или, в качестве альтернативы, использовать списковое включение (list comprehensions) или выражения-генераторы (generator expressions), чтобы сделать ваш код более питоническим и читабельным.
Для лучшего понимания работы map() вам были бы полезны некоторые знания о том, как работать с итерациями (iterables), циклами, функциями (functions) и лямбда-функций (lambda
functions).
Функциональный стиль в Python
В функциональном программировании вычисления выполняются путем объединения функций, которые принимают аргументы и возвращают конкретное значение (или значения). Эти функции не изменяют свои входные аргументы и не изменяют состояние программы. Они просто предоставляют результат данного вычисления. Такие функции обычно называются чистыми функциями (pure functions).
Теоретически программы, построенные с использованием функционального стиля, проще:
- Разрабатывать, потому что вы можете кодировать и использовать каждую функцию изолированно
- Отлаживать и тестировать, потому что вы можете тестировать и отлаживать отдельные функции, не глядя на остальную часть программы
- Понимать, потому что вам не нужно иметь дело с изменениями состояния на протяжении всей программы
Функциональное программирование обычно использует списки, массивы и другие итерационные объекты для представления данных вместе с набором функций, которые работают с этими данными и преобразовывают их. Когда дело доходит до обработки данных в функциональном стиле, обычно используются как минимум три метода:
- Сопоставление (Mapping) заключается в применении функции преобразования к итерируемому объекту для создания нового объекта. Элементы в новой итерации создаются путем вызова функции преобразования для каждого элемента в исходной итерации.
- Фильтрация (Filtering) состоит из применения предиката или булевозначной функции (predicate or Boolean-valued function) к итерируемому объекту для создания нового итерируемого объекта. Элементы в новой итерации создаются путем фильтрации любых элементов в исходной итерации, которые заставляют функцию предиката возвращать false.
- Сокращение (Reducing) состоит из применения функции reduce к итерируемому объекту для получения единственного накопленного значения.
По словам Guido van Rossum, на Python в большей степени влияют императивные языки программирования, чем функциональные языки:
Я никогда не считал, что Python находится под сильным влиянием функциональных языков, независимо от того, что люди говорят или думают. Я был более знаком с императивными языками, такими как C и Algol 68, и хотя я сделал функции первоклассными объектами (first-class objects), я не рассматривал Python как язык функционального программирования. (Источник)
Однако еще в 1993 году сообщество Python требовало некоторых функций функционального программирования. Они просили:
- Анонимные функции
- Функцию
map()
- Функцию
filter()
- Функцию
reduce()
Эти функциональные возможности были добавлены в язык благодаря участию многих членов сообщества. В настоящее время map()
, filter()
и reduce()
являются фундаментальными компонентами стиля функционального программирования в Python.
В этом руководстве мы рассмотрим одну из этих функциональных возможностей — встроенную карту функций map(). Вы также узнаете, как использовать составные части списковых включений (comprehensions) и выражения генератора (generator expressions), чтобы получить ту же функциональность, что и map(), в питоническом и удобочитаемом виде.
Начало работы с map в Python
Иногда вы можете столкнуться с ситуациями, когда вам нужно выполнить одну и ту же операцию со всеми элементами массива, чтобы создать новый массив. Самый быстрый и распространенный подход к этой проблеме — использовать цикл for в Python. Однако вы также можете решить эту проблему без явного использования циклов, используя map().
В следующих трех разделах вы узнаете, как работает map() и как вы можете использовать егр для обработки и преобразования итераций без циклов.
Что такое map()
map() перебирает элементы итерируемого массива (или коллекции) и возвращает новый массив (или итерируемый объект), который является результатом применения функции преобразования к каждому элементу исходного итерабельного массива.
Согласно документации, map() принимает функцию и итерацию (или несколько итераций) в качестве аргументов и возвращает итератор, который выдает преобразованные элементы по запросу. Сигнатура функции map определяется следующим образом:
map(function, iterable[, iterable1, iterable2,..., iterableN])
map()
применяет функцию к каждому элементу в итерируемом цикле и возвращает новый итератор, который по запросу возвращает преобразованные элементы. function может быть любая функция Python, которая принимает принимать аргументы, равное количеству итераций, которые вы передаете map().
Примечание. Первый аргумент map() — это объект функция, что означает, что вам нужно передать функцию, не вызывая ее. То есть без пары скобок.
Первый аргумент map() — функция преобразования. Другими словами, это функция, которая преобразует каждый исходный элемент в новый (преобразованный) элемент. Несмотря на то, что документация Python вызывает эту функцию аргумента, она может быть любой вызываемой Python. Сюда входят встроенные функции, классы, методы, лямбда-функции и пользовательские функции.
Операция, выполняемая map(), обычно известна как сопоставление, потому что она сопоставляет каждый элемент во входном итерируемом элементе с новым элементом в итоговом итерируемом. Для этого map() применяет функцию преобразования ко всем элементам во входной итерации.
Чтобы лучше понять map(), предположим, что вам нужно взять список числовых значений и преобразовать его в список, содержащий квадратное значение каждого числа в исходном списке. В этом случае вы можете использовать цикл for и написать что-то вроде этого
>>> numbers = [1, 2, 3, 4, 5] >>> squared = [] >>> for num in numbers: ... squared.append(num ** 2) ... >>> squared [1, 4, 9, 16, 25]
Когда вы запускаете этот цикл для чисел, вы получаете список квадратных значений. Цикл for перебирает числа и применяет к каждому значению операцию возведения в квадрат. Наконец, он сохраняет полученные значения в squared.
Вы можете добиться того же результата без использования явного цикла for, используя map(). Взгляните на следующую реализацию приведенного выше примера
>>> def square(number): ... return number ** 2 ... >>> numbers = [1, 2, 3, 4, 5] >>> squared = map(square, numbers) >>> list(squared) [1, 4, 9, 16, 25]
square() — это функция преобразования, которая преобразует число в его квадратное значение. Вызов map() применяет square() ко всем значениям и возвращает итератор, который возвращает квадратные значения. Затем вызывается list() для map(), чтобы создать объект списка, содержащий квадратные значения.
Поскольку map() написан на C и сильно оптимизирован, его внутренний подразумеваемый цикл может быть более эффективным, чем обычный цикл for в Python. Это одно из преимуществ использования map().
Второе преимущество использования map() связано с потреблением памяти. С помощью цикла for вам нужно сохранить весь список в памяти вашей системы. С помощью map() вы получаете элементы по запросу, и только один элемент находится в памяти вашей системы в данный момент.
Примечание. В Python 2.x map() возвращает список. Это поведение изменилось в Python 3.x. Теперь map() возвращает объект map, который является итератором, выдающим элементы по запросу. Вот почему вам нужно вызвать list(), чтобы создать желаемый объект списка.
В качестве другого примера предположим, что вам нужно преобразовать все элементы в списке из строки в целое число. Для этого вы можете использовать map() вместе с int() следующим образом:
>>> str_nums = ["4", "8", "6", "5", "3", "2", "8", "9", "2", "5"] >>> int_nums = map(int, str_nums) >>> int_nums <map object at 0x7fb2c7e34c70> >>> list(int_nums) [4, 8, 6, 5, 3, 2, 8, 9, 2, 5] >>> str_nums ["4", "8", "6", "5", "3", "2", "8", "9", "2", "5"]
map() применяет int()
к каждому значению в str_nums. Поскольку map() возвращает итератор (объект map), вам понадобится вызов list(), чтобы вы могли превратить его в объект списка. Обратите внимание, что исходная последовательность не изменяется в процессе
Использование map() с различными видами функций
Вы можете использовать любую функцию Python, вызываемую с помощью map(). Единственным условием будет то, что вызываемый объект принимает аргумент и возвращает конкретное и полезное значение. Например, вы можете использовать классы, экземпляры, реализующие специальный метод с именем __call__
, методы экземпляра, методы класса, статические методы и функции.
Есть несколько встроенных функций, которые вы можете использовать с map(). Рассмотрим следующие примеры:
>>> numbers = [-2, -1, 0, 1, 2] >>> abs_values = list(map(abs, numbers)) >>> abs_values [2, 1, 0, 1, 2] >>> list(map(float, numbers)) [-2.0, -1.0, 0.0, 1.0, 2.0] >>> words = ["Welcome", "to", "Real", "Python"] >>> list(map(len, words)) [7, 2, 4, 6]
Вы можете использовать любую встроенную функцию с map() при условии, что функция принимает аргумент и возвращает значение.
Когда дело доходит до использования map(), вы обычно видите использование лямбда-функции в качестве первого аргумента. лямбда-функции удобны, когда вам нужно передать функцию на основе выражений в map(). Например, вы можете повторно реализовать пример квадратных значений с помощью лямбда-функции следующим образом:
>>> numbers = [1, 2, 3, 4, 5] >>> squared = map(lambda num: num ** 2, numbers) >>> list(squared) [1, 4, 9, 16, 25]
лямбда-функции весьма полезны, когда дело доходит до использования map(). Они могут играть роль первого аргумента map(). Вы можете использовать лямбда-функции вместе с map () для быстрой обработки и преобразования ваших итераций.
Обработка множественных итераций с помощью map()
Если вы предоставляете несколько итераций для map(), тогда функция преобразования должна принимать столько аргументов, сколько итераций, которые вы передаете. Каждая итерация map() будет передавать одно значение из каждой итерации в качестве аргумента функции. Итерация останавливается в конце самой короткой итерации.
Рассмотрим следующий пример, в котором используется pow():
>>> first_it = [1, 2, 3] >>> second_it = [4, 5, 6, 7] >>> list(map(pow, first_it, second_it)) [1, 32, 729]
pow() принимает два аргумента, x и y, и возвращает x в степени y. На первой итерации x будет 1, y будет 4, а результат будет 1. Во второй итерации x будет 2, y будет 5, а результат будет 32, и так далее. Последняя итерация равна длине самой короткой итерации, которой в данном случае является first_it.
Этот метод позволяет объединить две или более итерации числовых значений, используя различные виды математических операций. Вот несколько примеров, в которых лямбда-функции используются для выполнения различных математических операций с несколькими входными итерациями.:
>>> list(map(lambda x, y: x - y, [2, 4, 6], [1, 3, 5])) [1, 1, 1] >>> list(map(lambda x, y, z: x + y + z, [2, 4], [1, 3], [7, 8])) [10, 15]
В первом примере используется операция вычитания, чтобы объединить две итерации по три элемента в каждой. Во втором примере складывается значения трех итераций.
Преобразование итераций строк с помощью map()
Когда вы работаете с итерациями строковых объектов, вам может быть интересно преобразовать все объекты с помощью какой-либо функции преобразования. В таких ситуациях вам может помочь map(). В следующих разделах вы познакомитесь с некоторыми примерами того, как использовать map() для преобразования итераций строковых объектов.
Использование методов str
Довольно распространенный подход к манипуляциям со строками — использование некоторых методов класса str для преобразования заданной строки в новую строку. Если вы имеете дело с итерациями строк и вам нужно применить одно и то же преобразование к каждой строке, вы можете использовать map() вместе с различными строковыми методами:
>>> string_it = ["processing", "strings", "with", "map"] >>> list(map(str.capitalize, string_it)) ['Processing', 'Strings', 'With', 'Map'] >>> list(map(str.upper, string_it)) ['PROCESSING', 'STRINGS', 'WITH', 'MAP'] >>> list(map(str.lower, string_it)) ['processing', 'strings', 'with', 'map']
Есть несколько преобразований, которые вы можете выполнить для каждого элемента в string_it, используя методы map() и string. В большинстве случаев вы будете использовать методы, которые не принимают дополнительных аргументов, например str.capitalize(), str.lower(), str.swapcase(), str.title() и str.upper().
Вы также можете использовать некоторые методы, которые принимают дополнительные аргументы со значениями по умолчанию, например str.strip(), которая принимает необязательный аргумент char, который по умолчанию удаляет пробелы:
>>> with_spaces = ["processing ", " strings", "with ", " map "] >>> list(map(str.strip, with_spaces)) ['processing', 'strings', 'with', 'map']
Когда вы используете str.strip() таким образом, вы полагаетесь на значение по умолчанию для char. В этом случае вы используете map(), чтобы удалить все пробелы в элементах with_spaces.
Примечание. Если вам нужно предоставить аргументы, а не полагаться на значение по умолчанию, вы можете использовать лямбда-функцию.
Вот пример, в котором для удаления точек используется str.strip(), а не пробелы по умолчанию:
>>> with_dots = ["processing..", "...strings", "with....", "..map.."] >>> list(map(lambda s: s.strip("."), with_dots)) ['processing', 'strings', 'with', 'map']
Лямбда-функция вызывает .strip() для строкового объекта s и удаляет все начальные и конечные точки.
Этот метод может быть удобен, когда, например, вы обрабатываете текстовые файлы, в которых строки могут иметь завершающие пробелы (или другие символы), и вам необходимо их удалить. Если это так, то вам необходимо учитывать, что использование str.strip() без специального символа также удалит символ новой строки.
Удаление знаков препинания
Когда дело доходит до обработки текста, вам иногда нужно удалить знаки препинания, которые остаются после того, как вы разбили текст на слова. Чтобы справиться с этой проблемой, вы можете создать настраиваемую функцию, которая удаляет знаки препинания из одного слова, используя регулярное выражение, которое соответствует наиболее распространенным знакам препинания.
Вот возможная реализация этой функции с помощью sub(), которая представляет собой функцию регулярного выражения, которая находится в модуле re в стандартной библиотеке Python:
>>> import re >>> def remove_punctuation(word): ... return re.sub(r'[!?.:;,"()-]', "", word) >>> remove_punctuation("...Python!") 'Python'
Внутри remove_punctuation() вы используете шаблон регулярного выражения, который соответствует наиболее распространенным знакам препинания, которые вы найдете в любом тексте, написанном на английском языке. Вызов re.sub() заменяет совпадающие знаки препинания пустой строкой («») и возвращает очищенное слово.
Имея функцию преобразования, вы можете использовать map() для запуска преобразования для каждого слова в вашем тексте. Вот как это работает:
>>> text = """Some people, when confronted with a problem, think ... "I know, I'll use regular expressions." ... Now they have two problems. Jamie Zawinski""" >>> words = text.split() >>> words ['Some', 'people,', 'when', 'confronted', 'with', 'a', 'problem,', 'think' , '"I', 'know,', "I'll", 'use', 'regular', 'expressions."', 'Now', 'they', 'have', 'two', 'problems.', 'Jamie', 'Zawinski'] >>> list(map(remove_punctuation, words)) ['Some', 'people', 'when', 'confronted', 'with', 'a', 'problem', 'think', 'I', 'know', "I'll", 'use', 'regular', 'expressions', 'Now', 'they', 'have ', 'two', 'problems', 'Jamie', 'Zawinski']
В этом фрагменте текста некоторые слова содержат знаки препинания. Например, у вас есть «people,» вместо «people», «problem,» вместо «problem» и так далее. Вызов map() применяет remove_punctuation() к каждому слову и удаляет все знаки препинания. Итак, во втором списке вы вычистили слова.
Обратите внимание, что апостроф (‘) отсутствует в вашем регулярном выражении, потому что вы хотите сохранить сокращения, такие как I'll
, такими, какие они есть.
Реализация алгоритма шифрования Цезаря
Юлий Цезарь, римский государственный деятель, использовал для защиты сообщений, которые он отправлял своим генералам шифр. Шифр Цезаря сдвигает каждую букву на количество букв. Например, если сдвинуть букву а на три, получится буква d и так далее.
Если сдвиг выходит за пределы конца алфавита, вам просто нужно вернуться к началу алфавита. В случае поворота на три x станет a. Вот как будет выглядеть алфавит после поворота:
- Оригинальный алфавит:
abcdefghijklmnopqrstuvwxyz
- Алфавит повернут на три:
defghijklmnopqrstuvwxyzabc
Следующий код реализует функцию rotate_chr(), которая принимает символ и поворачивает его на три. rotate_chr() вернет повернутый символ. Вот код:
1 def rotate_chr(c): 2 rot_by = 3 3 c = c.lower() 4 alphabet = "abcdefghijklmnopqrstuvwxyz" 5 6 if c not in alphabet: 7 return c 8 rotated_pos = ord(c) + rot_by 9 10 if rotated_pos <= ord(alphabet[-1]): 11 return chr(rotated_pos) 12 13 return chr(rotated_pos - len(alphabet))
Внутри rotate_chr() вы сначала проверяете, находится ли символ в алфавите. Если нет, то вы возвращаете тот же символ. Это позволяет сохранить знаки препинания и другие необычные символы. В строке 8 вы вычисляете новую повернутую позицию символа в алфавите. Для этого вы используете встроенную функцию ord().
ord() принимает символ Юникода и возвращает целое число, представляющее кодовую точку Юникода входного символа. Например, ord(«a») возвращает 97, а ord(«b») возвращает 98:
>>> ord("a") 97 >>> ord("b") 98
ord() принимает символ в качестве аргумента и возвращает кодовую точку Unicode входного символа.
Если вы добавите это целое число к целевому числу rot_by, вы получите повернутую позицию новой буквы в алфавите. В этом примере rot_by равно 3. Итак, буква «a», повернутая на три, станет буквой в позиции 100, то есть буквой «d». Буква «b», повернутая на три, станет буквой в позиции 101, то есть буквой «е», и так далее.
Если новая позиция буквы не выходит за пределы позиции последней буквы (alphabet[-1]), то вы возвращаете букву в этой новой позиции. Для этого вы используете встроенную функцию chr().
chr() является обратным к ord(). Он принимает целое число, представляющее кодовую точку Unicode символа Unicode, и возвращает символ в этой позиции. Например, chr(97) вернет ‘a‘, а chr(98) вернет ‘b‘:
>>> chr(97) 'a' >>> chr(98) 'b'
chr() принимает целое число, представляющее кодовую точку Unicode символа, и возвращает соответствующий символ.
Наконец, если новая повернутая позиция находится за пределами позиции последней буквы (alphabet[-1]), вам нужно повернуть обратно в начало алфавита. Для этого вам нужно вычесть длину алфавита из повернутой позиции (rotated_pos — len (alphabet)), а затем вернуть букву в этой новой позиции с помощью chr().
Используя rotate_chr() в качестве функции преобразования, вы можете использовать map() для шифрования любого текста с помощью алгоритма шифрования Цезаря. Вот пример, в котором str.join() используется для объединения строки:
>>> "".join(map(rotate_chr, "My secret message goes here.")) 'pb vhfuhw phvvdjh jrhv khuh.'
Строки также являются итерируемыми в Python. Итак, вызов map() применяет rotate_chr() к каждому символу исходной входной строки. В этом случае «M» становится «p», «y» становится «b» и так далее. Наконец, вызов str.join() объединяет каждый повернутый символ в окончательное зашифрованное сообщение.
Преобразование итераций чисел с помощью map
map() также имеет большой потенциал, когда дело доходит до обработки и преобразования итераций числовых значений. Вы можете выполнять широкий спектр математических и арифметических операций, преобразовывать строковые значения в числа с плавающей запятой или целые числа и т. д.
В следующих разделах вы рассмотрите несколько примеров того, как использовать map() для обработки и преобразования итераций чисел.
Использование математических операций
Типичным примером использования математических операций для преобразования итерации числовых значений является использование оператора возведения в степень (**). В следующем примере вы кодируете функцию преобразования, которая принимает число и возвращает число в квадрате и кубе:
>>> def powers(x): ... return x ** 2, x ** 3 ... >>> numbers = [1, 2, 3, 4] >>> list(map(powers, numbers)) [(1, 1), (4, 8), (9, 27), (16, 64)]
powers() принимает число x и возвращает его квадрат и куб. Поскольку Python обрабатывает несколько возвращаемых значений как кортежи, каждый вызов powers() возвращает кортеж с двумя значениями. Когда вы вызываете map() с powers() в качестве аргумента, вы получаете список кортежей, содержащий квадрат и куб каждого числа во входном итеративном элементе.
Существует множество математических преобразований, которые вы можете выполнить с помощью map(). Вы можете добавлять константы и вычитать их из каждого значения. Вы также можете использовать некоторые функции из математического модуля, такие как sqrt(), factorial(), sin(), cos() и т. д. Вот пример использования factorial():
>>> import math >>> numbers = [1, 2, 3, 4, 5, 6, 7] >>> list(map(math.factorial, numbers)) [1, 2, 6, 24, 120, 720, 5040]
В этом случае вы преобразуете числа в новый список, содержащий факториал каждого числа в исходном списке.
Вы можете выполнять широкий спектр математических преобразований итерации чисел с помощью map(). Насколько далеко вы углубитесь в эту тему, будет зависеть от ваших потребностей и вашего воображения. Подумайте об этом и напишите свои собственные примеры!
Преобразование температур
Другой вариант использования map() — преобразование единиц измерения. Предположим, у вас есть список температур, измеренных в градусах Цельсия или Фаренгейта, и вам нужно преобразовать их в соответствующие температуры в градусах Фаренгейта или Цельсия.
Для выполнения этой задачи вы можете закодировать две функции преобразования:
def to_fahrenheit(c): return 9 / 5 * c + 32 def to_celsius(f): return (f - 32) * 5 / 9
to_fahrenheit() измеренные температуры в градусах Цельсия и преобразует их в градусы Фаренгейта. Точно так же to_celsius() принимает температуру в градусах Фаренгейта и преобразует в градусы Цельсия.
Эти функции будут вашими функциями преобразования. Вы можете использовать их с map() для преобразования повторяемых измерений температуры в градусы Фаренгейта и Цельсия соответственно:
>>> celsius_temps = [100, 40, 80] >>> # Convert to Fahrenheit >>> list(map(to_fahrenheit, celsius_temps)) [212.0, 104.0, 176.0] >>> fahr_temps = [212, 104, 176] >>> # Convert to Celsius >>> list(map(to_celsius, fahr_temps)) [100.0, 40.0, 80.0]
Если вы вызовете map() с to_fahrenheit() и celsius_temps, то получите список мер температуры в градусах Фаренгейта. Если вы вызовете map() с to_celsius() и fahr_temps, вы получите список измерений температуры в градусах Цельсия.
Чтобы расширить этот пример и охватить любые другие виды преобразования единиц, вам просто нужно закодировать соответствующую функцию преобразования.
Преобразование строк в числа
При работе с числовыми данными вы, вероятно, столкнетесь с ситуациями, когда все ваши данные являются строковыми значениями. Для дальнейших вычислений вам нужно будет преобразовать строковые значения в числовые значения. map() тоже может помочь в этих ситуациях.
Если вы уверены, что ваши данные чистые и не содержат неправильных значений, вы можете использовать float() или int() в соответствии с вашими потребностями. Вот несколько примеров:
>>> # Convert to floating-point >>> list(map(float, ["12.3", "3.3", "-15.2"])) [12.3, 3.3, -15.2] >>> # Convert to integer >>> list(map(int, ["12", "3", "-15"])) [12, 3, -15]
В первом примере вы используете float() с map() для преобразования всех значений из строковых значений в значения с плавающей запятой. Во втором случае вы используете int() для преобразования строки в целое число. Обратите внимание: если одно из значений не является допустимым числом, вы получите ошибку ValueError..
Если вы не уверены, что ваши данные чистые, вы можете использовать более сложную функцию преобразования, например следующую:
>>> def to_float(number): ... try: ... return float(number.replace(",", ".")) ... except ValueError: ... return float("nan") ... >>> list(map(to_float, ["12.3", "3,3", "-15.2", "One"])) [12.3, 3.3, -15.2, nan]
Внутри to_float() используется оператор try, который улавливает ValueError, если float() не работает при преобразовании числа. Если ошибки не возникает, функция возвращает число, преобразованное в допустимое число с плавающей запятой. В противном случае вы получите значение nan (не число), которое представляет собой специальное значение с плавающей запятой, которое вы можете использовать для представления значений, которые не являются допустимыми числами, как и «One» в приведенном выше примере.
Вы можете настроить to_float() в соответствии с вашими потребностями. Например, вы можете заменить оператор return float («nan») на оператор return 0.0 и так далее.
Комбинирование map() с другими функциональными инструментами
До сих пор мы рассмотрели, как использовать map() для выполнения различных задач, связанных с итерациями. Однако, если вы используете map() вместе с другими функциональными инструментами, такими как filter() и reduce(), вы можете выполнять более сложные преобразования для своих итераций. Это то, о чем я собираюсь рассказать в следующих двух разделах.
map()
и filter()
Иногда вам нужно обработать массив и вернуть другой массив, которая является результатом фильтрации нежелательных значений во входной итерации. В этом случае может подойти filter(). filter() — это встроенная функция, которая принимает два позиционных аргумента:
function
будет предикатом или функцией с логическим значением, функцией, которая возвращает True или False в соответствии с входными данными.iterable
будет любым итерабельным объектом Python.
filter() возвращает элементы итерации, для которых функция возвращает True. Если вы передадите None в функцию, тогда filter() будет использовать функцию идентификации. Это означает, что filter() проверяет значение истинности каждого элемента в итерации и отфильтровывает все элементы, которые являются ложными.
Для иллюстрации, как можно использовать map() вместе с filter(), скажем, вам нужно вычислить квадратный корень из всех значений в списке. Поскольку ваш список может содержать отрицательные значения, вы получите сообщение об ошибке, потому что квадратный корень не определен для отрицательных чисел:
>>> import math >>> math.sqrt(-16) Traceback (most recent call last): File "<input>", line 1, in <module> math.sqrt(-16) ValueError: math domain error
Если в качестве аргумента указано отрицательное число, math.sqrt() вызывает ошибку ValueError. Чтобы избежать этой проблемы, вы можете использовать filter(), чтобы отфильтровать все отрицательные значения, а затем найти квадратный корень из оставшихся положительных значений. Посмотрите на следующий пример:
>>> import math >>> def is_positive(num): ... return num >= 0 ... >>> def sanitized_sqrt(numbers): ... cleaned_iter = map(math.sqrt, filter(is_positive, numbers)) ... return list(cleaned_iter) ... >>> sanitized_sqrt([25, 9, 81, -16, 0]) [5.0, 3.0, 9.0, 0.0]
is_positive() — это функция-предикат, которая принимает число в качестве аргумента и возвращает True, если число больше или равно нулю. Вы можете передать is_positive() в filter(), чтобы удалить все отрицательные числа. Таким образом, вызов map() будет обрабатывать только положительные числа, а math.sqrt() не выдаст вам ValueError.
map()
и reduce()
Функция reduce() находится в модуле под названием functools в стандартной библиотеке Python. reduce() — еще один основной функциональный инструмент в Python, который полезен, когда вам нужно применить функцию к итерируемому объекту и уменьшить его до одного накопительного значения. Этот вид операции обычно известен как сокращение или складывание. reduce() принимает два обязательных аргумента:
function
может быть любым вызываемым объектом Python, который принимает два аргумента и возвращает значение.iterable
может быть любым итерируемым объектом Python.
reduce() применяет функцию ко всем элементам в итерации и кумулятивно вычисляет окончательное значение.
Вот пример, который объединяет map() и reduce() для вычисления общего размера всех файлов, которые находятся в вашем домашнем каталоге, в совокупности:
>>> import functools >>> import operator >>> import os >>> import os.path >>> files = os.listdir(os.path.expanduser("~")) >>> functools.reduce(operator.add, map(os.path.getsize, files)) 4377381
В этом примере вызывается os.path.expanduser("~")
, чтобы получить путь к вашему домашнему каталогу. Затем вызывается os.listdir()
по этому пути, чтобы получить список с путями всех файлов, которые там находятся.
Вызов map() использует os.path.getsize()
для получения размера каждого файла. Наконец, используется reduce() с operator.add()
, чтобы получить совокупную сумму размера каждого отдельного файла. Конечный результат — это общий размер всех файлов в вашем домашнем каталоге в байтах.
Примечание. Несколько лет назад Google разработал и начал использовать модель программирования, которую они назвали MapReduce. Это был новый стиль обработки данных, предназначенный для управления большими данными с использованием параллельных и распределенных вычислений в кластере.
Эта модель была вдохновлена комбинацией операций map и reduce, обычно используемых в функциональном программировании.
Модель MapReduce оказала огромное влияние на способность Google обрабатывать огромные объемы данных в разумные сроки. Однако к 2014 году Google больше не использовал MapReduce в качестве основной модели обработки.
В настоящее время вы можете найти несколько альтернативных реализаций MapReduce, таких как Apache Hadoop, который представляет собой набор программных утилит с открытым исходным кодом, использующих модель MapReduce.
Несмотря на то, что вы можете использовать reduce() для решения проблемы, описанной в этом разделе, Python предлагает другие инструменты, которые могут привести к более питоническому и эффективному решению. Например, вы можете использовать встроенную функцию sum() для вычисления общего размера файлов в вашем домашнем каталоге:
>>> import os >>> import os.path >>> files = os.listdir(os.path.expanduser("~")) >>> sum(map(os.path.getsize, files)) 4377381
Этот пример намного удобнее и эффективнее, чем предыдущий. Если вы хотите глубже понять, как использовать reduce() и какие альтернативные инструменты вы можете использовать для замены reduce() питоническим способом, то ознакомьтесь с Python’s reduce(): From Functional to Pythonic Style.
Обработка итераций на основе кортежей с помощью starmap()
Функция itertools.starmap() в Python создает итератор, который применяет функцию к аргументам, полученным из итерации кортежей, и возвращает результаты. Это полезно, когда вы обрабатываете итерации, которые уже сгруппированы в кортежи.
Основное различие между map() и starmap() заключается в том, что последний вызывает свою функцию преобразования с помощью оператора unpack() для распаковки каждого кортежа аргументов в несколько позиционных аргументов. Итак, функция преобразования вызывается как функция ( args) вместо функции (arg1, arg2, … argN).
В официальной документации для starmap() говорится, что эта функция примерно эквивалентна следующей функции Python:
def starmap(function, iterable): for args in iterable: yield function(*args)
Цикл for в этой функции выполняет итерацию по элементам в итерации и в результате выдает преобразованные элементы. Вызов function(* args) использует оператор unpack для распаковки кортежей в несколько позиционных аргументов. Вот несколько примеров того, как работает starmap():
>>> from itertools import starmap >>> list(starmap(pow, [(2, 7), (4, 3)])) [128, 64] >>> list(starmap(ord, [(2, 7), (4, 3)])) Traceback (most recent call last): File "<input>", line 1, in <module> list(starmap(ord, [(2, 7), (4, 3)])) TypeError: ord() takes exactly one argument (2 given)
В первом примере используется pow() для вычисления степени первого значения, возведенного во второе значение в каждом кортеже. Кортежи будут в форме (base, exponent).
Если каждый кортеж в вашей итерации имеет два элемента, тогда функция также должна принимать два аргумента. Если кортежи содержат три элемента, функция должна принимать три аргумента и так далее. В противном случае вы получите ошибку TypeError
.
Если вы используете map() вместо starmap(), вы получите другой результат, потому что map() берет по одному элементу из каждого кортежа:
>>> list(map(pow, (2, 7), (4, 3))) [16, 343]
Обратите внимание, что map() принимает два кортежа вместо списка кортежей. map() также принимает одно значение из каждого кортежа на каждой итерации. Чтобы заставить map() возвращать тот же результат, что и starmap(), вам нужно поменять местами значения:
>>> list(map(pow, (2, 4), (7, 3))) [128, 64]
В этом случае у вас есть два кортежа вместо списка кортежей. Вы также поменяли местами 7 и 4. Теперь первый кортеж предоставляет основания, а второй кортеж — показатели.
Кодирование в питоническом стиле: замена map()
Инструменты функционального программирования, такие как map(), filter() и reduce(), существуют уже давно. Однако списковые включения (list comprehensions) и выражения генератора (generator expressions) стали их естественной заменой почти в каждом случае использования.
Например, функциональность, предоставляемая map(), почти всегда лучше выражается с использованием list comprehensions или generator expressions. В следующих двух разделах вы узнаете, как заменить вызов map() list comprehensions или generator expressions, чтобы сделать ваш код более читабельным и питоническим.
Использование list comprehension
Существует общий шаблон, который можно использовать для замены вызова map() list comprehension. Вот как:
# Generating a list with map list(map(function, iterable)) # Generating a list with a list comprehension [function(x) for x in iterable]
Обратите внимание, что list comprehension почти всегда читается более просто, чем вызов map(). Поскольку list comprehension довольно популярны среди разработчиков Python, их можно найти повсюду. Таким образом, замена вызова map() list comprehension сделает ваш код более знакомым для других разработчиков Python.
Вот пример того, как заменить map() для построения списка чисел в квадрате:
>>> # Transformation function >>> def square(number): ... return number ** 2 >>> numbers = [1, 2, 3, 4, 5, 6] >>> # Using map() >>> list(map(square, numbers)) [1, 4, 9, 16, 25, 36] >>> # Using a list comprehension >>> [square(x) for x in numbers] [1, 4, 9, 16, 25, 36]
Если вы сравните оба решения, то можете сказать, что тот, который использует list comprehension, более читабелен, потому что он читается почти как обычный английский. Кроме того, list comprehension позволяет избежать необходимости явно вызывать list() в map() для построения окончательного списка.
Использование generator expressions
map() возвращает объект, который является итератором, выдающим элементы по запросу. Итак, естественной заменой map() является generator expressions, поскольку оно возвращают объекты, которые также являются итераторами, дающими элементы по запросу.
Как известно, итераторы Python довольно эффективны с точки зрения потребления памяти. По этой причине map() теперь возвращает итератор вместо списка.
Между list comprehension и generator expressions есть крошечная синтаксическая разница. В первом используется пара квадратных скобок ([]) для ограничения выражения. Во втором используются круглые скобки (()). Итак, чтобы превратить list comprehension в generator expressions, вам просто нужно заменить квадратные скобки круглыми скобками.
Вы можете использовать generator expressions для написания кода, который читается лучше, чем код, использующий map(). Посмотрите на следующий пример:
>>> # Transformation function >>> def square(number): ... return number ** 2 >>> numbers = [1, 2, 3, 4, 5, 6] >>> # Using map() >>> map_obj = map(square, numbers) >>> map_obj <map object at 0x7f254d180a60> >>> list(map_obj) [1, 4, 9, 16, 25, 36] >>> # Using a generator expression >>> gen_exp = (square(x) for x in numbers) >>> gen_exp <generator object <genexpr> at 0x7f254e056890> >>> list(gen_exp) [1, 4, 9, 16, 25, 36]
Этот код имеет главное отличие от кода из предыдущего раздела: вы меняете квадратные скобки на пару круглых скобок, чтобы превратить list comprehension в generator expressions.
Generator expressions обычно используются в качестве аргументов в вызовах функций. В этом случае вам не нужно использовать круглые скобки для создания generator expressions, потому что круглые скобки, которые вы используете для вызова функции, также предоставляют синтаксис для построения генератора. Используя эту идею, вы можете получить тот же результат, что и в приведенном выше примере, вызвав list() следующим образом:
>>> list(square(x) for x in numbers) [1, 4, 9, 16, 25, 36]
Если вы используете generator expressions в качестве аргумента при вызове функции, вам не нужна дополнительная пара круглых скобок. Скобки, которые вы используете для вызова функции, обеспечивают синтаксис для построения генератора.
Generator expressions столь же эффективны, как map(), с точки зрения потребления памяти, потому что оба они возвращают итераторы, которые выдают элементы по запросу. Однако generator expressions почти всегда улучшают читаемость вашего кода. Они также делают ваш код более Pythonic в глазах других разработчиков Python.
Заключение
Функция map() в Python позволяет выполнять операции сопоставления (mapping) с итерациями. Операция сопоставления состоит из применения функции преобразования к элементам в итерации для создания преобразованной итерации. В общем, map() позволяет обрабатывать и преобразовывать итерации без использования операторов цикла.
В этом руководстве вы узнали, как работает map() и как использовать ее для обработки итераций. Вы также узнали о некоторых инструментах Pythonic, которые можно использовать для замены map() в своем коде.
Теперь вы знаете, как:
- Работать с map в Python
- Использовать map() для обработки и преобразования итераций без использования опереторов цикла
- Объединить map() с такими функциями, как filter() и reduce(), чтобы выполнять сложные преобразования
- Заменить map () такими инструментами, как list comprehensions и generator expressions
Обладая этими новыми знаниями, вы сможете использовать map() в своем коде придерживаясь функционального стиля программирования. Вы также можете переключиться на более питонический и современный стиль, заменив map() на list comprehension или generator expression.