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, and Reduce는 함수형 프로그래밍의 패러다임입니다. 이들은 프로그래머(즉, 여러분)가 반복문과 분기와 같은 복잡한 내부 동작에 신경 쓰지 않고도 더 간단하고 짧은 코드를 작성할 수 있게 해줍니다.
본질적으로, 이 세 가지 함수는 여러 반복 가능한 객체에 함수를 한 번에 적용할 수 있게 해줍니다. map
과 filter
는 Python에 내장되어 있으며(__builtins__
모듈에 존재) 별도의 import가 필요하지 않습니다. 그러나 reduce
는 functools
모듈에 있기에 import가 필요합니다. 이제 각각이 어떻게 작동하는지 알아보겠습니다. 먼저 map
부터 시작합니다.
Map
Python의 map()
함수는 다음과 같은 구문을 가집니다:
map(func, *iterables)
여기서 func
는 각 iterables
의 요소들에 적용할 함수입니다(여러 개일 수 있습니다). iterables
에 붙어있는 별표(*
)를 주목하세요. 이는 가능한 많은 숫자의 반복 가능한 객체를 전달할 수 있다는 것을 의미하며, 단, func
의 요구 인자 수와 일치해야 합니다. 예제를 보기 전에 다음 사항들을 숙지해야 합니다:
- Python 2에서는
map()
함수가 리스트를 반환합니다. 그러나 Python 3에서는map
객체라는 제너레이터 객체를 반환합니다. 결과를 리스트로 얻으려면, 내장된list()
함수를map
객체에 호출하면 됩니다. 즉,list(map(func, *iterables))
func
의 인수 수는 나열된iterables
의 수와 같아야 합니다.
이제 이 규칙들이 어떻게 작동하는지 예제를 통해 알아보겠습니다.
제가 가장 좋아하는 애완동물 이름들의 리스트(iterable
)가 전부 소문자로 되어있다고 가정해봅시다, 이들을 대문자로 바꿀 필요가 있습니다. 기존의 파이썬 코드에서는 이렇게 할 것입니다:
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
함수에 대한 두 번째 인수로 작용합니다(각 반복 시 필요한 소수점 자리 수). 그래서 circle_areas
를 통해 map
이 반복함에 따라, 첫 번째 반복에서는 circle_areas
의 첫 번째 요소인 3.56773
과 range(1,7)
의 첫 번째 요소인 1
이 round
에 전달되어, 실제로 round(3.56773, 1)
이 됩니다. 두 번째 반복에서는 두 번째 요소인 5.57668
과 range(1,7)
의 두 번째 요소인 2
가 전달되어 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()
함수는 여러 반복 가능한 객체를 받아서 각각의 요소를 포함하는 튜플을 생성합니다. Python 3에서 map()
처럼, 제너레이터 객체를 반환하며 내장된 list
함수를 호출하여 쉽게 리스트로 변환할 수 있습니다. map()
을 사용하여 커스텀 zip()
을 만들기 전에 아래 인터프리터 세션을 사용하여 zip()
을 이해해 보십시오.
# 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()
)을 사용하는 것이 허용되지 않는다는 의미는 아닙니다. 여전히 가능합니다. 저는 단지 더 적은 코드를 쓰는 방법을 선호했던 것뿐입니다(더 "Pythonic").
이상으로 map에 대한 설명이 끝났습니다. 이제 filter()
로 넘어갑니다.
Filter
map()
이 iterable의 각 요소를 함수에 통과시키고 함수의 결과를 반환하는 반면, filter()
는 먼저 함수가 불리언 값(참 또는 거짓)을 반환하도록 요구한 다음, iterable의 각 요소를 함수에 통과시키며, 거짓으로 평가된 요소를 "필터링"합니다. 다음과 같은 구문을 가집니다:
filter(func, iterable)
filter()
와 관련하여 다음 사항을 주의해야 합니다:
map()
과 달리, 하나의 iterable만이 필요합니다.func
인수는 불리언 타입을 반환해야 합니다. 그렇지 않으면filter
는 단순히 전달된iterable
을 반환합니다. 또한 하나의 iterable만 필요하므로,func
는 암묵적으로 하나의 인수만을 가져야 합니다.filter
는iterable
의 각 요소를 통해func
를 통과시키고, 참으로 평가된 요소들만 반환합니다. 이름 그대로, "필터"입니다.
몇 가지 예제를 보겠습니다
다음은 10명의 학생이 치른 화학 시험의 점수 목록(iterable
)입니다. 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
는 반복 가능한 객체의 각 요소에 누적적으로 적용할 함수이며, initial
은 선택적으로 계산 전에 반복 가능한 객체의 요소 앞에 위치하게 되는 값이며, 반복 가능한 객체가 비었을 때 기본값으로 작용합니다. reduce()
에 대해 주의해야 할 점은 다음과 같습니다:
1. func
는 두 개의 인수를 요구하며, 첫 번째 인수는 iterable
의 첫 번째 요소입니다(initial
이 제공되지 않은 경우). 두 번째 인수는 iterable
의 두 번째 요소입니다. 만약 initial
이 제공된다면, 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
의 다음 요소(세 번째)를 두 번째 요소로 적용합니다. 이는 numbers
가 소진될 때까지 계속 됩니다.
optional
값을 사용할 때 어떤 일이 일어나는지 보겠습니다.
# 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
의 첫 번째 인수로 사용했기 때문입니다.
이상으로 Python의 Map, Reduce, Filter에 대한 설명이 끝났습니다. 각 함수에 대한 이해를 확실히 하기 위해 아래 연습 문제들을 시도해 보십시오.
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!