Python

파이썬 함수형 프로그래밍 완전 정복: 핵심 원리부터 `map`, `filter`, `reduce` 활용법까지 깔끔 정리!

드리프트2 2025. 4. 27. 14:39

파이썬 함수형 프로그래밍 완전 정복: 핵심 원리부터 map, filter, reduce 활용법까지 깔끔 정리!

안녕하세요!

 

오늘은 파이썬(Python)에서 함수형 프로그래밍(Functional Programming)을 어떻게 활용할 수 있는지 쉽고 자세하게 알아보려고 합니다.

1. 함수형 프로그래밍(Functional Programming)이란 무엇일까요?

함수형 프로그래밍이란, 코드의 모든 부분이 변경할 수 없고(immutable), 순수 함수(pure function)로 이루어지는 프로그래밍 스타일을 말하는데요.

 

여기서 순수 함수란 다른 부분의 영향을 받지 않고, 동일한 입력값을 넣으면 언제나 동일한 결과값을 내놓는 함수를 의미합니다.

 

마치 수학 시간에 배우는 함수 y = f(x)처럼, x값이 같으면 y값도 항상 같은 그런 느낌이랄까요?

 

게다가 함수형 프로그래밍은 함수를 다른 함수의 인자(argument)로 전달하거나, 함수 자체를 결과로 반환(return)할 수 있다는 아주 특별한 특징도 가지고 있습니다.

 

이게 왜 특별한지는 차차 알게 되실 겁니다!

2. 순수 함수(Pure Function)의 예시를 살펴볼까요?

예를 들어, 숫자 리스트(list)에 담긴 값들을 각각 두 배로 만들고 싶다고 가정해 보죠.

 

아마 많은 분들이 아래와 같은 함수를 떠올리실 수 있습니다.

def multiply_2(list_data):
    for index in range(0, len(list_data)):
        list_data[index] *= 2
    return list_data

 

하지만 이 코드는 리스트 안의 값들을 직접 수정하기 때문에 순수 함수라고 하기에는 조금 부족합니다.

multiply_2() 함수를 여러 번 호출하면 호출할 때마다 결과가 달라질 수 있기 때문인데요.

 

즉, 원본 데이터가 계속 변하는 것이죠.

 

그럼 multiply_2() 함수를 순수 함수로 만들려면 어떻게 해야 할까요?

 

정답은 바로, 원본은 그대로 두고 새로운 리스트를 만들어서 그 결과를 반환하는 것입니다.

 

마치 복사본에 작업을 하는 것처럼요!

def multiply_2_pure(list_data):
    new_list = []
    for item in list_data:
        new_list.append(item * 2)
    return new_list

 

이렇게 하면 multiply_2_pure() 함수는 언제 호출하든 같은 입력값에 대해 항상 같은 결과를 내놓고, 원본 리스트 list_data는 전혀 건드리지 않게 됩니다.

 

이게 바로 순수 함수의 핵심입니다!

3. 함수형 프로그래밍의 장점과 단점은 무엇일까요?

함수형 프로그래밍의 가장 큰 장점은 뭐니 뭐니 해도 순수 함수변경 불가능한(immutable) 특징 덕분에 프로그램이 훨씬 안정적이고, 오류를 찾거나(디버깅, debugging) 테스트(test)하기가 쉬워진다는 점입니다.

 

어떤 함수가 문제를 일으키는지 명확하게 알 수 있으니까요.

 

반면에 단점도 있는데요, 아무래도 지켜야 할 규칙이 많다 보니 코드를 작성하는 것이 처음에는 다소 까다롭고 어렵게 느껴질 수 있습니다.

 

모든 것을 함수로 만들고, 상태 변화를 최소화하려다 보면 생각보다 복잡해질 때도 있거든요.

 

여기서 잠깐! 스칼라(Scala)와 같은 완전한 함수형 프로그래밍 언어에서는 함수 내부에 '변수'라는 개념 자체가 거의 없어서, 입력값이 정해지면 출력값은 무조건! 똑같다는 것을 보장할 수 있습니다.

 

하지만 우리가 다루는 파이썬(Python)처럼 변수 사용을 허용하는 프로그래밍 언어에서는 함수 내부 변수의 상태가 어떻게 변할지 모르기 때문에, 같은 입력이라도 상황에 따라 다른 결과가 나올 가능성이 여전히 남아있습니다.

 

파이썬(Python)은 변수 사용을 허용하기 때문에 엄밀히 말해 순수 함수형 프로그래밍 언어는 아닙니다.

 

다만, 함수형 프로그래밍 스타일을 어느 정도 지원하는데요, 주로 map(), filter(), reduce()라는 세 가지 특별한 함수를 통해 그 맛을 볼 수 있습니다.

 

이 함수들은 보통 람다(lambda) 익명 함수와 짝을 이뤄 사용되곤 한데요. 이제부터 이 세 친구들을 하나씩 자세히 알아보도록 하겠습니다!

4. 파이썬(Python)의 함수형 프로그래밍 지원 함수들

파이썬에서는 함수형 프로그래밍을 돕는 유용한 내장 함수들을 제공하는데요, 대표적인 세 가지를 집중 탐구해 보겠습니다.

 

파이썬 map() 함수

 

map() 함수의 기본 사용법은 다음과 같습니다.

map(함수, 반복 가능한 객체)

 

여기서 함수 매개변수(parameter)는 말 그대로 전달할 함수를 의미하는데요, 파이썬에 원래 있는 내장 함수일 수도 있고, 우리가 직접 만든 함수, 또는 이름 없는 한 줄짜리 함수인 람다(lambda) 익명 함수가 될 수도 있습니다.

반복 가능한 객체는 리스트(list)나 문자열(string)처럼 하나씩 차례대로 꺼내서 처리할 수 있는 데이터 묶음을 뜻합니다.

 

그럼 map() 함수는 무슨 일을 할까요? 바로 반복 가능한 객체의 각 요소(element)에 대해 지정된 함수를 한 번씩 실행하고, 그 결과들을 모아서 map 객체라는 특별한 형태로 돌려주는 역할을 합니다.

 

여기서 중요한 점은 map 객체는 그 자체로는 내용을 바로 볼 수 없다는 것인데요.

 

그래서 보통 for 반복문이나 list() 함수를 사용해서 그 결과를 확인하곤 합니다.

 

[예제 1] 리스트의 각 요소를 2배로 만들기

listDemo = [1, 2, 3, 4, 5]
new_list = map(lambda x: x * 2, listDemo)
print(list(new_list))

 

실행 결과:

[2, 4, 6, 8, 10]

 

정말 간단하게 각 숫자가 두 배가 되었죠?

lambda x: x * 2 부분이 바로 이름 없는 함수로, 입력받은 x를 2배로 만들어 돌려주는 역할을 합니다.

 

[예제 2] map() 함수에 여러 반복 가능한 객체를 인자로 전달하기

 

map() 함수는 여러 개의 반복 가능한 객체를 동시에 처리할 수도 있습니다.

listDemo1 = [1, 2, 3, 4, 5]
listDemo2 = [3, 4, 5, 6, 7]
new_list = map(lambda x, y: x + y, listDemo1, listDemo2)
print(list(new_list))

 

실행 결과:

[4, 6, 8, 10, 12]

 

이번에는 listDemo1listDemo2에서 같은 위치에 있는 숫자끼리 더하는 작업을 map 함수가 깔끔하게 처리해 줬습니다.

 

참고로, map() 함수는 사실 C 언어로 직접 만들어져 있어서 실행할 때 파이썬 인터프리터(Python interpreter)를 거치지 않고 내부적으로 많은 최적화 과정을 거칩니다.

 

그래서 비슷한 작업을 하는 다른 방법들(예: for 반복문)에 비해 실행 속도가 매우 빠르다는 장점이 있답니다.

 

데이터가 많을수록 그 차이가 확 느껴지죠!

 

파이썬 filter() 함수

 

filter() 함수의 기본 사용법은 map()과 아주 비슷합니다.

filter(함수, 반복 가능한 객체)

 

여기서도 함수 매개변수는 전달할 함수를, 반복 가능한 객체는 처리할 데이터 묶음을 의미합니다.

 

filter() 함수의 역할은 이름 그대로 '걸러내는' 것입니다. 반복 가능한 객체의 각 요소에 대해 함수를 적용해서 그 결과가 참(True)인지 거짓(False)인지를 판단합니다.

 

그리고 최종적으로 참(True)을 반환하는 요소들만 모아서 새로운 순회 가능한 컬렉션(collection)을 만들어 돌려줍니다.

 

즉, 조건에 맞는 데이터만 쏙쏙 골라내는 것이죠.

 

[예제 3] 리스트에서 모든 짝수만 골라내기

listDemo = [1, 2, 3, 4, 5]
new_list = filter(lambda x: x % 2 == 0, listDemo) # x를 2로 나눈 나머지가 0이면 짝수!
print(list(new_list))

 

실행 결과:

[2, 4]

 

lambda x: x % 2 == 0 함수가 각 숫자가 짝수인지(True) 아닌지(False)를 판단해서 짝수만 new_list에 담아주었습니다.

 

[예제 4] filter() 함수에 여러 반복 가능한 객체 전달하기 (원문에서는 map 함수 예시로 되어 있지만, filter 함수 설명 부분에 있어 그대로 번역합니다.

 

실제로는 아래 코드는 map 함수의 동작을 보여줍니다.)

listDemo = [1, 2, 3, 4, 5] # 이 변수는 아래 예제에서 직접 사용되지 않습니다.
new_list = map(lambda x,y: x-y>0,[3,5,6],[1,5,8] )
print(list(new_list))

 

실행 결과:

[True, False, False]

 

이 예제는 map 함수를 사용해서 두 리스트의 같은 위치에 있는 요소들을 뺀 결과가 0보다 큰지(True) 아닌지(False)를 보여줍니다.

(3-1 > 0 -> True, 5-5 > 0 -> False, 6-8 > 0 -> False)

 

파이썬 reduce() 함수

 

reduce() 함수는 이름에서 알 수 있듯이 데이터 묶음(컬렉션)에 대해 어떤 연산을 '누적적으로' 수행할 때 주로 사용됩니다.

 

예를 들어, 리스트 안의 모든 숫자를 더하거나 곱할 때 아주 유용한데요.

 

기본 사용법은 다음과 같습니다.

reduce(함수, 반복 가능한 객체)

 

여기서 함수는 반드시 두 개의 매개변수를 가져야 하며, 반복 가능한 객체는 처리 대상 데이터입니다.

reduce() 함수는 이 함수반복 가능한 객체의 요소들에 대해 왼쪽에서 오른쪽으로 차례차례 적용하여, 최종적으로 하나의 결과값으로 줄여나갑니다.

 

한 가지 중요한 점은, reduce() 함수는 파이썬 3.x(Python 3.x) 버전부터는 기본 내장 함수 목록에서 빠지고 functools라는 모듈(module)로 이사를 갔다는 것입니다.

 

그래서 이 함수를 사용하려면 먼저 functools 모듈을 가져와야(import) 합니다.

 

[예제 5] 리스트 요소들의 곱 계산하기

import functools # functools 모듈을 먼저 불러옵니다.

listDemo = [1, 2, 3, 4, 5]
product = functools.reduce(lambda x, y: x * y, listDemo)
print(product)

 

실행 결과:

120

 

reduce 함수는 lambda x, y: x * y 함수를 이용해서 다음과 같이 계산을 수행합니다.

  1. 처음 두 요소 1과 2에 대해 1 * 2 = 2를 계산합니다.
  2. 이전 결과 2와 다음 요소 3에 대해 2 * 3 = 6을 계산합니다.
  3. 이전 결과 6과 다음 요소 4에 대해 6 * 4 = 24를 계산합니다.
  4. 이전 결과 24와 다음 요소 5에 대해 24 * 5 = 120을 계산합니다.
    이렇게 모든 요소에 대해 누적 곱셈을 수행하여 최종 결과 120을 얻었습니다.

5. 요약 및 정리

자, 지금까지 파이썬에서 함수형 프로그래밍의 기본 개념과 map(), filter(), reduce() 함수에 대해 자세히 알아봤는데요.

 

일반적으로 컬렉션(리스트, 튜플 등) 안의 요소들에 대해 어떤 연산을 수행할 때, 그 연산이 덧셈, 곱셈, 조건에 따른 필터링처럼 비교적 간단한 작업이라면 for 반복문 대신 map(), filter(), reduce() 함수를 먼저 고려해 보는 것이 좋습니다.

 

코드가 간결해지고, 특히 map 함수의 경우 실행 효율도 높기 때문입니다.

 

또한, 데이터 양이 매우 클 때 (예를 들어 빅데이터 처리나 머신 러닝(machine learning) 분야에서)는 함수형 프로그래밍 방식이 병렬 처리에 유리하고 효율성이 더 높은 경우가 많아 선호되곤 합니다.

 

물론 데이터 양이 많지 않거나, 컬렉션의 요소들에 대해 좀 더 복잡한 로직이나 여러 단계의 연산을 수행해야 한다면, 코드의 가독성(읽기 쉬운 정도)을 고려해서 전통적인 for 반복문을 사용하는 것이 더 나을 수도 있습니다.

 

어떤 도구를 선택할지는 상황에 따라, 그리고 팀의 스타일에 따라 달라질 수 있다는 점을 기억해 주시면 좋겠습니다.