Get started learning Python with DataCamp's free Intro to Python tutorial. Learn Data Science by completing interactive coding challenges and watching videos by expert instructors. Start Now!
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join 11 million other learners and get started learning Python for data science today!
Good news! You can save 25% off your Datacamp annual subscription with the code LEARNPYTHON23ALE25 - Click here to redeem your discount
Карта, Фильтр, Уменьшение
Map, Filter и Reduce являются парадигмами функционального программирования. Они позволяют программисту писать более простой и короткий код, не заставляя при этом углубляться в такие детали, как циклы и ветвление.
По сути, эти три функции позволяют вам применить функцию к ряду итерируемых объектов за один раз. map
и filter
встроены в Python (в модуль __builtins__
) и не требуют импорта. Однако, чтобы использовать reduce
, его нужно импортировать из модуля functools
. Давайте лучше поймем, как все это работает, начиная с map
.
Map
Функция map()
в Python имеет следующий синтаксис:
map(func, *iterables)
Где func
- это функция, которую нужно применить к каждому элементу в iterables
(столько, сколько их есть). Обратите внимание на звездочку (*
) в iterables
? Это означает, что может быть любое количество итерируемых объектов, при условии, что func
имеет это количество аргументов, необходимых для входа. Прежде чем перейти к примеру, важно отметить следующее:
- В Python 2 функция
map()
возвращает список. Однако в Python 3 функция возвращаетmap object
, который является генераторным объектом. Чтобы получить результат в виде списка, на объект карты можно вызвать встроенную функциюlist()
, т.е.list(map(func, *iterables))
. - Количество аргументов для
func
должно совпадать с количеством перечисленныхiterables
.
Давайте посмотрим, как эти правила применяются в следующих примерах.
Допустим, у меня есть список (iterable
) моих любимых имен домашних животных, все в нижнем регистре, и мне нужно их в верхнем регистре. Традиционно, в обычном Python, я бы сделал что-то вроде этого:
my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = []
for pet in my_pets:
pet_ = pet.upper()
uppered_pets.append(pet_)
print(uppered_pets)
Что в итоге выведет ['ALFRED', 'TABITHA', 'WILLIAM', 'ARLA']
С функцией map()
это не только проще, но и намного гибче. Я просто делаю так:
# Python 3
my_pets = ['alfred', 'tabitha', 'william', 'arla']
uppered_pets = list(map(str.upper, my_pets))
print(uppered_pets)
Которая выведет тот же результат. Обратите внимание, что, используя определенный выше синтаксис map()
, в данном случае func
— это str.upper
, а iterables
— это список my_pets
— всего один итерируемый объект. Также обратите внимание, что мы не вызывали функцию str.upper
(делая это: str.upper()
), так как функция map делает это за нас для каждого элемента в списке my_pets
.
Важнее всего отметить, что функция str.upper
по определению требует только одного аргумента, и поэтому мы передали только один итерируемый объект. Если функция, которую вы передаете, требует двух или трех, или n аргументов, то вы должны передать ей два, три или n итерируемых объектов. Позвольте прояснить это другим примером.
Допустим, у меня есть список площадей кругов, который я рассчитал где-то, все с пятью десятичными знаками. И мне нужно округлить каждый элемент списка до его позиции десятичных знаков, что означает, что я должен округлить первый элемент списка до одного десятичного знака, второй элемент списка до двух десятичных знаков, третий элемент списка до трех десятичных знаков и так далее. С использованием map()
это проще простого. Давайте посмотрим как.
Python уже благословил нас встроенной функцией round()
, которая принимает два аргумента - число для округления и количество знаков после запятой для округления числа. Таким образом, поскольку функция требует двух аргументов, мы должны передать два итерируемых объекта.
# Python 3
circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]
result = list(map(round, circle_areas, range(1, 7)))
print(result)
Видите, какая красота у map()
? Можете ли вы представить, какую гибкость это вызывает?
Функция range(1, 7)
действует как второй аргумент функции round
(количество требуемых десятичных знаков при каждой итерации). Таким образом, как map
проходит через circle_areas
, во время первой итерации, первый элемент circle_areas
, 3.56773
, передается вместе с первым элементом range(1,7)
, 1
функции round
, делая его фактически стать round(3.56773, 1)
. Во втором цикле второй элемент circle_areas
, 5.57668
вместе с вторым элементом range(1,7)
, 2
передается функции round
, что делает его переводом в round(5.57668, 2)
. Это происходит до конца списка circle_areas
.
Наверняка вы задаетесь вопросом: «Что произойдет, если я передам итерируемый объект, меньше или больше по длине чем первый итерируемый объект? То есть, что если я передам range(1, 3)
или range(1, 9999)
как второй итерируемый объект в указанной выше функции». И ответ прост: ничего! Ладно, это неправда. Происходит то, что функция map()
не выдаст никакой ошибки, она просто будет итерировать по элементам, пока не сможет найти второй аргумент для функции, в этот момент она просто остановится и вернет результат.
Так, например, если вы оцените result = list(map(round, circle_areas, range(1, 3)))
, вы не получите никакой ошибки даже при том, что длина circle_areas
и range(1, 3)
различаются. Вместо этого Python действует следующим образом: он берет первый элемент из circle_areas
и первый элемент из range(1,3)
и передает их в round
. round
оценивает его и сохраняет результат. Затем он переходит ко второй итерации, второму элементу из circle_areas
и второму элементу из range(1,3)
, round
снова сохраняет его. Теперь, во время третьей итерации (у circle_areas
есть третий элемент), Python берет третий элемент из circle_areas
и затем пытается взять третий элемент из range(1,3)
, но так как у range(1,3)
нет третьего элемента, Python просто останавливается и возвращает результат, который в данном случае будет просто [3.6, 5.58]
.
Вперед, попробуйте это.
# Python 3
circle_areas = [3.56773, 5.57668, 4.00914, 56.24241, 9.01344, 32.00013]
result = list(map(round, circle_areas, range(1, 3)))
print(result)
То же самое происходит, если длина circle_areas
меньше длины второго итерируемого объекта. Python просто останавливается, когда не может найти следующий элемент в одном из итерируемых объектов.
Чтобы закрепить наши знания о функции map()
, мы будем использовать ее для реализации нашей собственной настраиваемой функции zip()
. Функция zip()
принимает несколько итерируемых объектов и создает кортеж, содержащий каждый из элементов этих итерируемых объектов. Как и map()
, в Python 3 она возвращает генераторный объект, который может быть легко преобразован в список, вызвав на нем встроенную функцию list
. Используйте нижеприведенную сессию интерпретатора, чтобы понять суть zip()
перед тем, как мы создадим нашу собственную функцию с map()
# Python 3
my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1, 2, 3, 4, 5]
results = list(zip(my_strings, my_numbers))
print(results)
В качестве бонуса, можете угадать, что произойдет в вышеприведенной сессии, если my_strings
и my_numbers
будут не одинаковой длины? Нет? Поменяйте длину одного из них.
Переходим к нашей собственной настраиваемой функции zip()
!
# Python 3
my_strings = ['a', 'b', 'c', 'd', 'e']
my_numbers = [1, 2, 3, 4, 5]
results = list(map(lambda x, y: (x, y), my_strings, my_numbers))
print(results)
Только посмотрите на это! У нас тот же результат, что и у zip
.
Вы также заметили, что мне даже не нужно было создавать функцию стандартным способом def my_function()
? Это говорит о том, насколько гибка map()
, и Python в целом! Я просто использовал функцию lambda
. Это не означает, что использование стандартного метода определения функции (в виде def function_name()
) не допускается, оно все равно позволено. Я просто предпочел писать меньше кода (быть "питоничным").
На этом все о map
. Переходим к filter()
Filter
В то время как map()
передает каждый элемент в итерируемом через функцию и возвращает результат всех элементов, прошедших через функцию, filter()
сначала требует, чтобы функция возвращала булевые значения (true или false), а затем передает каждый элемент в итерируемом через функцию, "отфильтровывая" те, которые false. Она имеет следующий синтаксис:
filter(func, iterable)
Следующие пункты следует отметить относительно filter()
:
- В отличие от
map()
, требуется только один итерируемый объект. - Аргумент
func
должен возвращать булев тип. Если этого не происходит,filter
просто возвращает переданный емуiterable
. Также, поскольку требуется только один итерируемый объект, это подразумевает, чтоfunc
должен принимать только один аргумент. filter
передает каждый элемент в итерируемом черезfunc
и возвращает только те, которые оцениваются как true. Это прямо указано в названии — "фильтр".
Давайте рассмотрим несколько примеров
Следующий пример представляет собой список (iterable
) оценок 10 студентов на экзамене по химии. Давайте отфильтруем тех, кто прошел с результатами больше 75... используя filter
.
# Python 3
scores = [66, 90, 68, 59, 76, 60, 88, 74, 81, 65]
def is_A_student(score):
return score > 75
over_75 = list(filter(is_A_student, scores))
print(over_75)
Следующий пример будет детектором палиндромов. "Палиндром" - это слово, фраза или последовательность, которая читается одинаково как вперед, так и назад. Давайте отфильтруем слова, которые являются палиндромами, из кортежа (iterable
) предполагаемых палиндромов.
# Python 3
dromes = ("demigod", "rewire", "madam", "freer", "anutforajaroftuna", "kiosk")
palindromes = list(filter(lambda word: word == word[::-1], dromes))
print(palindromes)
Что должно вывести ['madam', 'anutforajaroftuna']
.
Довольно аккуратно, да? Наконец, reduce()
Reduce
reduce
применяется функцию двух аргументов к элементам итерируемого объекта кумулятивно, опционально начиная с начального аргумента. Она имеет следующий синтаксис:
reduce(func, iterable[, initial])
Где func
— это функция, по которой каждый элемент в iterable
получается кумулятивно применяется, а initial
— это необязательное значение, которое помещается перед элементами итерируемого объекта в расчете и служит значением по умолчанию, когда итерируемый объект пуст. Следует отметить следующее о reduce()
:
1. func
требует два аргумента, первый из которых является первым элементом в iterable
(если initial
не задан) и вторым элементом в iterable
. Если initial
задан, то он становится первым аргументом для func
, а первый элемент iterable
становится вторым элементом.
2. reduce
"сводит" (простите меня) iterable
к одному значению.
Как обычно, давайте рассмотрим несколько примеров.
Давайте создадим нашу собственную версию встроенной функции Python sum()
. Функция sum()
возвращает сумму всех элементов в итерируемом объекте, переданном ей.
# Python 3
from functools import reduce
numbers = [3, 4, 6, 9, 34, 12]
def custom_sum(first, second):
return first + second
result = reduce(custom_sum, numbers)
print(result)
Результат, как вы ожидаете, будет 68
.
Итак, что произошло?
Как обычно, все дело в итерациях: reduce
берет первый и второй элементы в numbers
и передает их в custom_sum
соответственно. custom_sum
вычисляет их сумму и возвращает её в reduce
. reduce
затем берет этот результат и применяет его в виде первого элемента в custom_sum
и третий элемент в numbers
, как второй элемент в custom_sum
. Это происходит постоянно (кумулятивно), пока numbers
не заканчиваются.
Давайте посмотрим, что произойдет, когда я использую необязательное значение initial
.
# Python 3
from functools import reduce
numbers = [3, 4, 6, 9, 34, 12]
def custom_sum(first, second):
return first + second
result = reduce(custom_sum, numbers, 10)
print(result)
Результат, как вы ожидаете, составит 78
, потому что reduce
сначала использует 10
в качестве первого аргумента для custom_sum
.
Вот и все о Map, Reduce и Filter в Python. Попробуйте выполнить указанные ниже упражнения, чтобы убедиться в своем понимании каждой функции.
Exercise
В этом упражнении вам нужно использовать каждую из map
, filter
, и reduce
, чтобы исправить ошибочный код.
from functools import reduce
# Use map to print the square of each numbers rounded
# to three decimal places
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
# Use filter to print only the names that are less than
# or equal to seven letters
my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"]
# Use reduce to print the product of these numbers
my_numbers = [4, 6, 9, 23, 5]
# Fix all three respectively.
map_result = list(map(lambda x: x, my_floats))
filter_result = list(filter(lambda name: name, my_names, my_names))
reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers, 0)
print(map_result)
print(filter_result)
print(reduce_result)
#### Map
from functools import reduce
my_floats = [4.35, 6.09, 3.25, 9.77, 2.16, 8.88, 4.59]
my_names = ["olumide", "akinremi", "josiah", "temidayo", "omoseun"]
my_numbers = [4, 6, 9, 23, 5]
map_result = list(map(lambda x: round(x ** 2, 3), my_floats))
filter_result = list(filter(lambda name: len(name) <= 7, my_names))
reduce_result = reduce(lambda num1, num2: num1 * num2, my_numbers)
print(map_result)
print(filter_result)
print(reduce_result)
test_output_contains("[18.922, 37.088, 10.562, 95.453, 4.666, 78.854, 21.068]")
test_output_contains("['olumide', 'josiah', 'omoseun']")
test_output_contains("24840")
success_msg("Congrats! Nice work.")
This site is generously supported by DataCamp. DataCamp offers online interactive Python Tutorials for Data Science. Join over a million other learners and get started learning Python for data science today!