Pixamp

Функции reduce, map и zip в Python

30.01.2019 10:20 CPython 3.7.2

The article image

Сегодня я предлагаю поговорить о нескольких весьма полезных функциях стандартной библиотеки Python: reduce(), map(), zip(), а также zip_longest() и starmap().

reduce

functools.reduce(function, iterable[, initializer])

Функция reduce() из модуля functools используется для сворачивания разного рода итерируемых объектов (iterables) в одно единственное значение.

Рассмотрим пример. Предположим, нам нужно получить произведение всех элементов списка s = [1, 2, 3, 4, 5]. Для решения такой задачи прекрасно подходит reduce():

>>> from functools import reduce
>>> s = [1, 2, 3, 4, 5]
>>> reduce(lambda x, y: x * y, s)
120

Я передаю reduce() два аргумента: один из них – это список s, другой – анонимная функция двух аргументов, которая возвращает произведение значений этих аргументов. Тут надо заметить, что в Python все является объектом, включая функции. Подобно тому, как можно передать ссылку на список s (который является объектом), так можно передать ссылку на функцию (которая тоже является объектом).

В данном случае, я использую анонимную функцию. Такая функция, как следует из названия, не имеет имени. Еще она не может быть многострочной. Для того, чтобы объявить анонимную функцию используется слово lambda, после которого через запятую перечисляются аргументы, а далее, после двоеточия, следует тело функции.

Стоит заметить, что если присвоить ссылку на анонимную функцию какой-нибудь переменной, то с ней можно обращаться также, как и с “обычной”, неанонимной функцией:

>>> mul = lambda x, y: x * y
>>> mul(5, 5)
25

Тот же результат, но используя неанонимную функцию:

>>> def mul(x, y):
...     return x * y
...
>>> mul(5, 5)
25

Конечно, reduce() я мог передать и неанонимную функцию:

>>> def mul(x, y):
...     return x * y
...
>>> reduce(mul, s)
120

Итак, что же делает reduce()? Мы уже разобрались, что reduce() принимает два аргумента: функцию и итерируемый объект. Это обязательные аргументы, которые мы должны передать, вызывая данную функцию.

Проиллюстрируем процесс работы reduce():

reduce

S – это итерируемый объект (числами обозначены позиции условных элементов: от 0 до N), а F – вызываемая функция. Столбец In отвечает за входные данные функции (то есть передаваемые аргументы на разных итерациях), а Out – за выходные данные (возвращаемые функцией результаты). Числа в ячейках столбцов In и Out – это шаги итерации: от 1 до Z.

Для N+1 элементов будет сделано Z шагов итерации.

Итерация 1. На данном этапе функции F передаются нулевой и первый элементы объекта S. Полученный результат вызова F сохраняется.

Итерация 2. F передаются ранее полученный результат вызова этой функции на этапе первой итерации, а также второй элемент S. Результат сохраняется.

Итерация 3. F передаются сохраненный результат вызова функции на этапе второй итерации, а также третий элемент S. Результат сохраняется.

И так далее, до последнего элемента итерируемого объекта S.

Итерация Z. Это заключительная итерация. F передаются результат функции на этапе Z-1-итерации и последний элемент N объекта S. Результат и будет возвращаемым значением reduce() (зеленая ячейка).

Это все в общем случае. Разберем теперь также по шагам наш пример:

>>> from functools import reduce
>>> s = [1, 2, 3, 4, 5]
>>> reduce(lambda x, y: x * y, s)
120

Итерация 1. Анонимной функции передаются 1 и 2. Возвращаемый результат – 2.

Итерация 2. Функции передаются полученный результат (2) и следующий элемент списка (3). Результат – 6.

Итерация 3. Для 6 и 4 возвращается 24.

Итерация 4. На последнем шаге передаются 24 и 5. Результат – 120.

reduce() также имеет третий, необязательный аргумент initializer. Если он задан, то он будет самым первым аргументом, который reduce() передаст вызываемой функции. Или, если последовательность окажется пустой, то initializer будет тем значением, которое вернет reduce():

>>> from functools import reduce
>>> from operator import add
>>> def multiadd(*args):
...     return reduce(add, args, 0)
...
>>> multiadd(1, 2, 3, 4, 5)  # (((((0 + 1) + 2) + 3) + 4) + 5)
15
>>> multiadd()
0

map

map(function, iterable, …)

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

Рассмотрим пример. Допустим, нам нужно возвести в квадрат каждый элемент списка s = [1, 2, 3, 4, 5]. Воспользуемся map() для решения данной задачи:

>>> s = [1, 2, 3, 4, 5]
>>> list(map(lambda x: pow(x, 2), s))
[1, 4, 9, 16, 25]

Примечание

В приведенном примере я использую list() для того, чтобы сделать результат работы map() нагляднее. Если исключить list(), то в результате вместо [1, 4, 9, 16, 25] мы увидим <map object at ...> (вместо многоточия будет адрес).

map() вызывает функцию pow() (возведение в степень) для каждого элемента списка s:

Итерация Функция Результат
1 pow(1, 2) 1
2 pow(2, 2) 4
3 pow(3, 2) 9
4 pow(4, 2) 16
5 pow(5, 2) 25

Стоит заметить, что map() умеет работать с несколькими итерируемыми объектами одновременно. Рассмотрим немного измененный пример, но который возвращает тот же результат:

>>> s = [1, 2, 3, 4, 5]
>>> s2 = [2] * 5
>>> list(map(lambda x, y: pow(x, y), s, s2))
[1, 4, 9, 16, 25]

Здесь у нас уже два списка: знакомый нам s = [1, 2, 3, 4, 5], а также новый s2 = [2] * 5. Второй список содержит степени для чисел первого списка. Так как я хотел получить тот же результат, что и в первом примере, то в списке s2 пока только двойки. То есть числа списка s будут возводиться в квадрат.

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

Для разнообразия изменим второй список, а также избавимся от конструкции с анонимной функцией, которая нам больше не нужна:

>>> s = [1, 2, 3, 4, 5]
>>> s2 = [1, 8, 9, 1, 2]
>>> list(map(pow, s, s2))
[1, 256, 19683, 4, 25]

Что произойдет, если map() передать последовательности разной длины? Чтобы выяснить это, добавим еще один элемент к s:

>>> s.append(6)
>>> s
[1, 2, 3, 4, 5, 6]

Сейчас, первый список (s) длиннее второго (s2) на один элемент. В этом случае, map() прекратит работу, как только закончится самый короткий список (а это s2), то есть до нового элемента (6) просто не дойдет очередь:

>>> list(map(lambda x, y: pow(x, y), s, s2))
[1, 256, 19683, 4, 25]

zip

zip(*iterables)

Функция zip() позволяет поэлементно агрегировать элементы переданных итерируемых объектов. Возвращает zip(), как и map(), специальный объект-итератор.

Традиционно, начнем с примера. Предположим, у нас есть два списка: один содержит имена людей, а другой – их возраст:

names = ['John', 'Emma', 'Noah', 'Mia', 'Abigail']
ages = [25, 30, 20, 22, 45]

Для удобства и последующей работы мы хотели бы объединить эти списки в один словарь people. Здесь нам и поможет функция zip():

>>> people = dict(zip(names, ages))
>>> people
{'John': 25, 'Emma': 30, 'Noah': 20, 'Mia': 22, 'Abigail': 45}

Проиллюстрируем процесс работы zip():

zip

zip() возвращает итератор кортежей. Что содержат эти кортежи? Если мы возьмем самый первый кортеж, то он будет содержать по одному самому первому элементу от каждого итерируемого объекта. Второй кортеж будет содержать вторые элементы этих объектов, третий – третьи и так далее.

Если передаваемые последовательности имеют разную длину, то как и в случае с map(), zip() прекратит работу, как только закончится самая короткая последовательность:

>>> names.append('Clare')
>>> names
['John', 'Emma', 'Noah', 'Mia', 'Abigail', 'Clare']
>>> people = dict(zip(names, ages))  # "Clare" не попадет словарь!
>>> people
{'John': 25, 'Emma': 30, 'Noah': 20, 'Mia': 22, 'Abigail': 45}

zip_longest

itertools.zip_longest(*iterables, fillvalue=None)

Впрочем, если не хочется терять ни одного элемента из-за разницы длин, то можно использовать родственную zip() функцию zip_longest() из модуля itertools.

Отличается от zip() она тем, что “короткие” последовательности дополняются fillvalue:

>>> from itertools import zip_longest
>>> names = ['John', 'Emma', 'Noah', 'Mia', 'Abigail']
>>> ages = [25, 30, 20, 22, 45]
>>> names.append('Clare')
>>> people = dict(zip_longest(names, ages, fillvalue='Unknown'))
>>> people
{'John': 25, 'Emma': 30, 'Noah': 20, 'Mia': 22, 'Abigail': 45, 'Clare': 'Unknown'}

starmap

itertools.starmap(function, iterable)

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

>>> s = [1, 2, 3, 4, 5]
>>> s2 = [1, 8, 9, 1, 2]
>>> list(starmap(pow, zip(s, s2)))
[1, 256, 19683, 4, 25]

Дата последнего редактирования статьи: 04.06.2019 16:51

Следующая статья › Контейнеры в Python
Предыдущая статья › Астериск в Python

Главная страница