map Python

Основы

Python – очень обширный язык. Он предоставляет возможность писать код как в объектно-ориентированном стиле, так и в функциональном. Один из основных инструментов для функционального стиля, который даёт программирование на Python — map(). Это функция, которая применяет любую другую функцию ко всем элементам какой-либо последовательности (или нескольких). В этом уроке Вы узнаете, как и зачем её использовать.

Функциональный стиль в Python

Функциональное программирование – это парадигма, которая рассматривает программу, как совокупность функций (в математическом смысле). Функция – это блок кода, который принимает входные данные, производит какую-то работу и возвращает результат. Если функция не имеет побочных эффектов – влияет только на данные, которые возвращает – её называют чистой функцией. Если большинство Ваших функций чистые – это хорошо, так как такие функции создают более стройную архитектуру, в которой проще разобраться, искать ошибки, вносить изменения и так далее.

Пример чистой функции:


Пример функции с побочным эффектом:

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

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

Функциональное программирование включает в себя, как минимум, следующие инструменты:

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

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

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

— Замыкания – функции, которые запоминают своё состояние.

— Анонимные функции – функции без имени. Представлены ключевым словом Python lambda (лямбда).

Не смотря на то, что Пайтон ориентирован в первую очередь на объектно-ориентированный стиль, в нём есть и другие инструменты из функционального стиля программирования, к примеру, функция Python zip(), которая объединяет несколько последовательностей, enumerate(), которая возвращает пары индекс-элемент, списковые включения и так далее.

map Python

map в Python 3 – это функция, которая принимает другую функцию и одну или несколько итерируемых объектов, применяет полученную функцию к элементам полученных итерируемых объектов и возвращает специальный объект map, который является итератором и содержит результаты. Самый простой способ получить результаты из итераторы, это преобразовать его в коллекцию – использовать функции list(), set() или tuple()

Функция Python map() имеет следующий синтаксис:

map(function, iterable, [iterable_2, iterable_3, ...])


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

collection = range(10)
def my_func(x):
    return x**4

print('map:', list(map(my_func, collection)))

print('Списковое включение:', [my_func(x) for x in collection])

result = []
for item in collection:
    result.append(my_func(item))

print('for:', result)
# Вывод:

map: [0, 1, 16, 81, 256, 625, 1296, 2401, 4096, 6561]

Списковое включение: [0, 1, 16, 81, 256, 625, 1296, 2401, 4096, 6561]

for: [0, 1, 16, 81, 256, 625, 1296, 2401, 4096, 6561]


Что выгодно отличает map(), так это скорость выполнения. Дело в том, что этой функции не нужно создавать копии элементов для вычислений и, из-за этого она часто оказывается быстрее альтернативных вариантов. Кроме того, вы можете отложить вычисления, если не станете сразу преобразовывать итератор в последовательность.

Давайте увеличим вычислительную сложность и замерим скорость выполнения всех трёх способов:

from time import time

collection = range(10 ** 7)

def my_func(x):
    return x ** 4 ** 4

now = time()
tuple(map(my_func, collection))
print('map:', time() - now)

now = time()
[my_func(x) for x in collection]
print('Списковое включение:', time() - now)

now = time()
result = []
for item in collection:
    result.append(my_func(item))
print('for:', time() - now)
# Вывод:

map: 134.0279233455658

Списковое включение: 135.18106865882874

for: 97.16599297523499


Как видите, map() оказалась быстрее спискового включения.

Ещё одним преимуществом является то, что map() может принимать несколько коллекций. Другими методами это сделать сложнее.

Первый аргумент: функция

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

  • Встроенные — это заранее созданные функции
  • Пользовательские – те, что пишет программист с помощью ключевого слова def
  • Анонимные – функции, у которых нет имени, и они объявляются при помощи ключевого слова lambda
  • Методы – функции, которые принадлежат определённым class objects

Пример map() c встроенными функциями


from math import sqrt

collection = range(10)
print(tuple(map(sqrt, collection)))
# Вывод:

(0.0, 1.0, 1.4142135623730951, 1.7320508075688772, 2.0, 2.23606797749979, 2.449489742783178, 2.6457513110645907, 2.8284271247461903, 3.0)


Здесь мы использовали встроенную функцию sqrt из модуля math стандартной библиотеки. Эта функция возвращает корень числа.

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


from random import randint

collection = [randint(100, 7000) for i in range(10)]

def my_func(var: int):
    print(chr(var), end=' ')

set(map(my_func, collection))
# Вывод:

Ӈ ر ᣐ ᧒ Ɵ ؆ ణ ག ᘁ ᎋ


Здесь мы сперва заполняем список случайными целыми числами, лежащими в диапазоне от 100 до 7000. Затем определяем пользовательскую функцию при помощи ключевого слова def. Она принимает целое число в параметр var и печатает в терминале символ, соответствующий этому числу. Последняя команда – выполнить пользовательскую функцию my_func к каждому элементу последовательности collection с помощью map(). Поскольку функция map возвращает объект map, его нужно конвертировать в последовательность. В данном случае итератор преобразуется в множество при помощи встроенной функции set().

Обратите внимание, что в функции my_func нет слова return – используется исключительно побочный эффект print. В таком случае значение всё равно возвращается, но оно равно None. Проверить это можно распечатав итератор map():

from random import randint

collection = [randint(100, 7000) for i in range(10)]

def my_func(var: int):
    print(chr(var), end=' ')

print('\n' + str(tuple(map(my_func, collection))))
# Вывод:

ᢢ ฌ Й ᄣ ͯ ݑ ᒀ მ ෍ ᄼ

(None, None, None, None, None, None, None, None, None, None)

Пример map() c анонимными функциями

Распространённый вариант использования функции map() – передача лямбда-функции.

Эти функции выглядят следующим образом:

lambda параметры: выражение

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

collection = range(10)

print(tuple(map(lambda x: x**x, collection)))
# Вывод:

(1, 1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489)


В этом примере lambda принимает один аргумент x и возвращает x в степени x. Как видите, такие функции прекрасно работают с map().

Пример map() c методами

Да, методы – это про объектно-ориентированное программирование. Но, в том и прелесть Python, что подходы можно комбинировать.

collection = range(10)
my_list = []
tuple(map(my_list.append, collection))
print(my_list)
# Вывод:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]


В этом примере мы создали переменную my_list типа список. Далее, в map() мы вызываем метод этого объекта .append(), который добавляет элементы в конец списка.

Второй аргумент: итерируемый объект

Наиболее часто встречающийся итерируемый тип в Питоне – это список. Это связано с тем, что объекты данного типа действительно очень удобны. Но, есть и другие: множество, кортеж, словарь, строка и другие.

collection = range(10)

my_list = list(collection)
my_tuple = tuple(collection)
my_set = set(collection)
my_dict = dict(enumerate(collection))
my_str = [str(i) for i in collection]

result_list = list()

tuple(map(result_list.append, my_list))
tuple(map(result_list.append, my_tuple))
tuple(map(result_list.append, my_set))
tuple(map(result_list.append, my_dict))
tuple(map(result_list.append, my_str))

print(result_list)
# Вывод:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']


Здесь Вы можете наблюдать как функция map() отработала со всеми перечисленными выше итерируемыми типами данных.

Обработка множественных итераций с помощью map()

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

collection = range(10)

def my_func(x: int, y: int):
    return x ** y

result_tuple = tuple(map(my_func, collection, collection))

print(result_tuple)
# Вывод:

(1, 1, 4, 27, 256, 3125, 46656, 823543, 16777216, 387420489)


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

Теперь пример с последовательностями разной длины:

collection = range(10)

def my_func(x: int, y: int):
    return (x, y)

result_tuple = tuple(map(my_func, collection, collection[:4:-1]))

print(result_tuple)
# Вывод:

((0, 9), (1, 8), (2, 7), (3, 6), (4, 5))


Здесь мы передаём вторую коллекцию вдвое короче первой. Как видите, в итоговом кортеже всего 5 элементов.

Возвращаемое значение: итератор

Функция Python map() возвращает специальный объект map, который является итератором. Как уже говорилось, его можно преобразовать в список, множество или кортеж с помощью встроенных функций:

collection = range(10)

def my_func(x: int, y: int):
    return (x, y)

result = tuple(map(my_func, collection, collection[:4:-1]))
print('tuple:', result)

result = list(map(my_func, collection, collection[:4:-1]))
print('list:', result)

result = set(map(my_func, collection, collection[:4:-1]))
print('set:', result)

result = dict(enumerate(map(my_func, collection, collection[:4:-1])))
print('dict:', result)
# Вывод:

tuple: ((0, 9), (1, 8), (2, 7), (3, 6), (4, 5))

list: [(0, 9), (1, 8), (2, 7), (3, 6), (4, 5)]

set: {(2, 7), (1, 8), (0, 9), (4, 5), (3, 6)}

dict: {0: (0, 9), 1: (1, 8), 2: (2, 7), 3: (3, 6), 4: (4, 5)}


А можно отложить вычисления до менее загруженного вычислениями места кода или вычислять итерации по одной в тот момент, когда это нужно:

collection = range(10)

def my_func(x: int, y: int):
    return (x, y)

map_object = map(my_func, collection, collection[:4:-1])
print(map_object.__next__())
print(map_object.__next__())
print(map_object.__next__())
print(list(map_object))
# Вывод:

(0, 9)

(1, 8)

(2, 7)

[(3, 6), (4, 5)]


В этом примере мы выполнили три раза по одной итерации при помощи дандер-метода .__next__(), а затем вычислили все оставшиеся итерации при помощи функции list().

MapReduce

MapReduce – это модель распределённых вычислений, разработанная в корпорации Google, применяемая в технологиях Big Data для распределённых вычислений над гигантскими массивами данных в узлах компьютерных кластеров.

MapReduce можно с уверенностью назвать главной технологией Big Data. Суть MapReduce состоит в разделении информационного массива на части, распределённой обработки каждой части на отдельном узле и финального объединения всех результатов.

Сегодня множество различных, как коммерческих, так и свободных продуктов, использующих эту модель распределенных вычислений: Apache Hadoop, Apache CouchDB, MongoDB, в графических процессорах NVIDIA с использованием CUDA, MySpace Qizmt и прочие.

Авторами данной модели считаются сотрудники компании Google Джеффри и Санджай Гемават, взявшие за основу две процедуры функционального программирования: map, применяющая нужную функцию к каждому элементу последовательности, и reduce, объединяющая результаты работы map.

Другими словами, упрощённо эта модель состоит в том, что массив данных делится на части, затем выполняются одинаковые вычисления на разных серверах с разными частями массива, а затем результаты вычислений вновь объединяются.

Предлагаю провести эксперимент и воссоздать MapReduce на Вашем собственном компьютере.

Пишем MapReduce

Вместо нескольких серверов будем использовать процессы. Процессы – это как независимые программы, которые могут вычисляться на разных ядрах процессора. Для этого нам понадобится модуль multiprocessing из стандартной библиотеки. В этом модуле есть объект Pool, представляющий из себя несколько процессов. У объекта Pool есть метод .map(), который идентичен уже изученной ранее функции map().

from multiprocessing import Pool
from time import time

def l(x):
    return x ** 1000

y = list(range(100000))
if __name__ == '__main__':
    a = Pool()

    now = time()
    sum(list(a.map(l, y)))
    print('Вычисления в пуле процессов заняли:', time() - now)

    now = time()
    sum(list(map(l, y)))
    print('Вычисления в одном процессе заняли:', time() - now)

    # Вывод:

Вычисления в пуле процессов заняли: 1.8741416931152344

Вычисления в одном процессе заняли: 4.924708127975464


Здесь роль reduce, то есть обобщения результатов, взяла на себя функция sum(), которая вычисляет сумму всех элементов последовательности.

Оцените статью
О Python на русском языке
Добавить комментарий

Adblock
detector