포스트

[Python 100일 챌린지] Day 66 - 함수형 프로그래밍 기초

[Python 100일 챌린지] Day 66 - 함수형 프로그래밍 기초

함수형 프로그래밍은 데이터 처리의 요리법이에요! 🍳✨

for 루프로 한 줄씩 처리하는 대신, map, filter, reduce를 써서 “짝수만 골라서, 2배로 만들고, 다 더해!” 같은 작업을 한 줄로 뚝딱 끝낼 수 있어요! Pandas, Spark 같은 데이터 분석 라이브러리들이 이 방식을 쓰고 있답니다. 😊 코드가 짧아지고 버그도 줄어들어요!

처음엔 낯설어도 익숙해지면 엄청 강력해요!

(35분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

📚 사전 지식

  • Phase 3: 함수와 람다
  • Phase 2: 리스트 컴프리헨션

🎯 학습 목표 1: 함수형 프로그래밍의 개념 이해하기

한 줄 설명

함수형 프로그래밍 = 레고 블록처럼 함수를 조립하는 방식 🧱🔧

작은 함수들을 쌓아 올려서 복잡한 작업을 만들어요!

1.1 함수형 프로그래밍이란?

핵심 원칙:

  1. 순수 함수(Pure Functions): 같은 입력 → 같은 출력, 부작용 없음
  2. 불변성(Immutability): 데이터를 변경하지 않아요
  3. 일급 함수(First-Class Functions): 함수를 값처럼 다뤄요
1
2
3
4
5
6
7
8
9
10
11
12
# 명령형 프로그래밍 (Imperative) - 어떻게 할지 명령
numbers = [1, 2, 3, 4, 5]
result = []
for n in numbers:
    if n % 2 == 0:
        result.append(n * 2)
print(result)  # [4, 8]

# 함수형 프로그래밍 (Functional) - 무엇을 할지 선언
numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2, filter(lambda x: x % 2 == 0, numbers)))
print(result)  # [4, 8]

차이가 뭘까요? 🤔

  • 명령형: “이렇게 저렇게 해라!” (How)
  • 함수형: “이걸 해줘!” (What)

💡 실생활 비유: 명령형은 “계단을 올라가서, 문을 열고, 불을 켜라”고 하는 거고, 함수형은 “방을 밝혀줘”라고 하는 거예요!

1.2 순수 함수 vs 비순수 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 순수 함수: 부작용 없음 ✅
def pure_add(a, b):
    return a + b

# 비순수 함수: 외부 상태 변경 ❌
total = 0
def impure_add(a, b):
    global total
    total = a + b  # 부작용!
    return total

# 순수 함수의 장점
print(pure_add(2, 3))  # 5
print(pure_add(2, 3))  # 5 (항상 같은 결과)

# 비순수 함수의 문제
print(impure_add(2, 3))  # 5
print(total)  # 5 (외부 상태 변경됨)

순수 함수의 장점! 🌟

  • 테스트하기 쉬워요 (항상 같은 결과)
  • 버그가 적어요 (부작용이 없으니까)
  • 병렬 처리가 안전해요 (서로 간섭 안 함)

💡 실생활 비유: 순수 함수는 자판기 같아요. 1000원 넣으면 항상 같은 음료가 나오고, 다른 자판기에 영향을 안 줘요!


🎯 학습 목표 2: map, filter, reduce 마스터하기

한 줄 설명

map/filter/reduce = 데이터 처리의 3종 세트 🔧🔨🪛

변환하고(map), 거르고(filter), 합치는(reduce) 작업의 필수 도구예요!

2.1 map: 변환 - 모든 걸 바꿔요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# map(function, iterable)
# 모든 요소에 함수 적용

# 기본 사용
numbers = [1, 2, 3, 4, 5]
squared = map(lambda x: x ** 2, numbers)
print(list(squared))  # [1, 4, 9, 16, 25]

# 여러 iterable 사용
list1 = [1, 2, 3]
list2 = [10, 20, 30]
result = map(lambda x, y: x + y, list1, list2)
print(list(result))  # [11, 22, 33]

# 실전: 문자열 처리
names = ['alice', 'bob', 'charlie']
capitalized = map(str.capitalize, names)
print(list(capitalized))  # ['Alice', 'Bob', 'Charlie']

# 실전: 타입 변환
strings = ['1', '2', '3', '4']
numbers = list(map(int, strings))
print(numbers)  # [1, 2, 3, 4]

map이 뭐예요? 🗺️

  • 리스트의 모든 요소에 같은 함수를 적용해요
  • 원본은 그대로 두고 새로운 결과를 만들어요

💡 실생활 비유: 포토샵의 일괄 처리 같아요! 사진 100장에 필터를 한 번에 적용하는 거죠!

2.2 filter: 필터링 - 원하는 것만 골라요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# filter(function, iterable)
# 조건을 만족하는 요소만 선택

# 기본 사용
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = filter(lambda x: x % 2 == 0, numbers)
print(list(evens))  # [2, 4, 6, 8, 10]

# 실전: 양수만 필터링
numbers = [-2, -1, 0, 1, 2, 3]
positives = filter(lambda x: x > 0, numbers)
print(list(positives))  # [1, 2, 3]

# 실전: 빈 문자열 제거
words = ['hello', '', 'world', '', 'python']
non_empty = filter(None, words)  # Truthy 값만 선택
print(list(non_empty))  # ['hello', 'world', 'python']

# 또는
non_empty = filter(lambda x: x, words)
print(list(non_empty))  # ['hello', 'world', 'python']

filter가 뭐예요? 🔍

  • 조건에 맞는 것만 골라내요
  • 체처럼 걸러내는 거예요!

💡 실생활 비유: 커피 필터 같아요! 원두 가루는 걸러지고 커피만 내려오죠!

2.3 reduce: 축약 - 다 합쳐요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from functools import reduce

# reduce(function, iterable, initial)
# 누적 계산

# 합계
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers)
print(total)  # 15

# 초기값 지정
total = reduce(lambda acc, x: acc + x, numbers, 10)
print(total)  # 25 (10 + 1 + 2 + 3 + 4 + 5)

# 최댓값 찾기
numbers = [3, 7, 2, 9, 1, 5]
maximum = reduce(lambda acc, x: acc if acc > x else x, numbers)
print(maximum)  # 9

# 실전: 문자열 연결
words = ['Hello', 'World', 'Python']
sentence = reduce(lambda acc, word: acc + ' ' + word, words)
print(sentence)  # Hello World Python

# 실전: 중첩 리스트 평탄화
nested = [[1, 2], [3, 4], [5, 6]]
flat = reduce(lambda acc, lst: acc + lst, nested, [])
print(flat)  # [1, 2, 3, 4, 5, 6]

reduce가 뭐예요? 📉

  • 여러 값을 하나로 합쳐요
  • 눈덩이처럼 굴러가면서 커져요!

💡 실생활 비유: 눈사람 만들 때 눈덩이를 굴리는 거예요! 작은 눈덩이가 점점 커지죠!

동작 원리 이해하기! 🤔

1
2
3
4
# reduce(lambda acc, x: acc + x, [1, 2, 3, 4])
# 1단계: acc=1, x=2 → 3
# 2단계: acc=3, x=3 → 6
# 3단계: acc=6, x=4 → 10

2.4 map + filter + reduce 조합 - 합체!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from functools import reduce

# 짝수의 제곱의 합
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

result = reduce(
    lambda acc, x: acc + x,          # 합계
    map(
        lambda x: x ** 2,            # 제곱
        filter(lambda x: x % 2 == 0, numbers)  # 짝수
    )
)
print(result)  # 220 (4 + 16 + 36 + 64 + 100)

# 더 읽기 쉬운 버전
evens = filter(lambda x: x % 2 == 0, numbers)  # 짝수만
squared = map(lambda x: x ** 2, evens)          # 제곱
total = reduce(lambda acc, x: acc + x, squared)  # 합계
print(total)  # 220

실무에서 이렇게 써요! 🎯

  • 데이터 분석에서 엄청 자주 써요
  • Pandas의 .apply(), .filter() 같은 메서드가 이 개념이에요!

🎯 학습 목표 3: 람다 함수 활용하기

한 줄 설명

람다 = 이름 없는 일회용 함수 🎭

간단한 함수를 빠르게 만들 때 써요!

3.1 람다 vs 일반 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
# 일반 함수
def add(a, b):
    return a + b

# 람다 함수 (익명 함수)
add_lambda = lambda a, b: a + b

print(add(2, 3))         # 5
print(add_lambda(2, 3))  # 5

# 람다는 한 줄 표현식만 가능
# multiply = lambda x, y: return x * y  # ❌ SyntaxError
multiply = lambda x, y: x * y  # ✅

언제 람다를 쓸까요? 🤔

  • 간단한 함수가 딱 한 번만 필요할 때
  • map/filter/sort의 인자로 쓸 때
  • 함수 이름이 필요 없을 때

💡 실생활 비유: 일회용 젓가락 같아요! 간단하게 한 번 쓰고 버려요. 복잡한 요리엔 제대로 된 도구(일반 함수)를 써야죠!

3.2 람다 활용 사례

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 정렬 키로 사용
students = [
    {'name': 'Alice', 'age': 25},
    {'name': 'Bob', 'age': 20},
    {'name': 'Charlie', 'age': 23}
]

# 나이순 정렬
sorted_by_age = sorted(students, key=lambda s: s['age'])
print(sorted_by_age)
# [{'name': 'Bob', 'age': 20}, ...]

# 이름순 정렬
sorted_by_name = sorted(students, key=lambda s: s['name'])
print(sorted_by_name)

# 튜플 정렬
pairs = [(1, 'one'), (3, 'three'), (2, 'two')]
sorted_pairs = sorted(pairs, key=lambda x: x[1])
print(sorted_pairs)
# [(1, 'one'), (3, 'three'), (2, 'two')]

실무에서 이렇게 써요! 💼

  • 데이터 정렬할 때 정렬 기준 지정
  • JSON 데이터 처리할 때
  • 간단한 콜백 함수로

3.3 람다의 한계 - 이럴 땐 안 돼요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ❌ 여러 줄 불가
# complex_lambda = lambda x:
#     if x > 0:
#         return x
#     else:
#         return -x

# ✅ 일반 함수 사용
def abs_value(x):
    if x > 0:
        return x
    else:
        return -x

# ❌ 가독성 저하
# 복잡한 로직은 일반 함수 사용 권장

람다의 한계! ⚠️

  • 한 줄 표현식만 가능해요
  • 복잡하면 오히려 읽기 어려워요
  • 디버깅하기 힘들어요 (이름이 없으니까)

🎯 학습 목표 4: 실전 함수형 프로그래밍

예제 1: 데이터 파이프라인 - 실무 스타일!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from functools import reduce

# 데이터 처리 파이프라인
sales_data = [
    {'product': 'A', 'price': 100, 'quantity': 2},
    {'product': 'B', 'price': 50, 'quantity': 5},
    {'product': 'C', 'price': 200, 'quantity': 1},
    {'product': 'D', 'price': 30, 'quantity': 10}
]

# 1. 총 매출 계산 (price * quantity)
revenues = map(lambda x: x['price'] * x['quantity'], sales_data)

# 2. 200 이상인 항목만 선택
high_revenues = filter(lambda x: x >= 200, revenues)

# 3. 합계
total = reduce(lambda acc, x: acc + x, high_revenues, 0)

print(f"고액 매출 합계: {total}")  # 500원

실무에서 이렇게 써요! 📊

  • 매출 데이터 분석
  • 로그 데이터 처리
  • API 응답 데이터 변환

예제 2: 텍스트 처리 - 자연어 처리의 기본!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
text = "Hello World Python Programming"

# 함수형 접근
result = list(map(
    lambda word: word.lower(),  # 소문자 변환
    filter(
        lambda word: len(word) > 5,  # 6글자 이상만
        text.split()
    )
))

print(result)  # ['python', 'programming']

# 더 읽기 쉬운 버전
words = text.split()
long_words = filter(lambda word: len(word) > 5, words)
lowercase_words = map(lambda word: word.lower(), long_words)
result = list(lowercase_words)
print(result)

예제 3: 중첩 데이터 처리 - 복잡한 데이터도 문제없어요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
from functools import reduce

# 중첩된 사용자 데이터
users = [
    {'name': 'Alice', 'orders': [100, 200, 150]},
    {'name': 'Bob', 'orders': [50, 75]},
    {'name': 'Charlie', 'orders': [300, 250, 100]}
]

# 모든 주문의 총합
total = reduce(
    lambda acc, user: acc + sum(user['orders']),
    users,
    0
)
print(f"총 주문액: {total}")  # 1225원

# 각 사용자의 평균 주문액
averages = list(map(
    lambda user: {
        'name': user['name'],
        'average': sum(user['orders']) / len(user['orders'])
    },
    users
))

for avg in averages:
    print(f"{avg['name']}: 평균 {avg['average']:.2f}")

예제 4: 함수 합성 - 함수를 조립해요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 함수 합성 (Function Composition)
def compose(*functions):
    """여러 함수를 합성"""
    def inner(arg):
        result = arg
        for func in reversed(functions):
            result = func(result)
        return result
    return inner

# 함수들 정의
add_10 = lambda x: x + 10
multiply_2 = lambda x: x * 2
square = lambda x: x ** 2

# 함수 합성
pipeline = compose(square, multiply_2, add_10)

print(pipeline(5))  # ((5 + 10) * 2) ** 2 = 900

# 또는
from functools import reduce

def pipe(*functions):
    """파이프라인 (왼쪽에서 오른쪽)"""
    return lambda arg: reduce(lambda result, func: func(result), functions, arg)

pipeline = pipe(add_10, multiply_2, square)
print(pipeline(5))  # ((5 + 10) * 2) ** 2 = 900

함수 합성이 뭐예요? 🔗

  • 여러 함수를 연결해서 하나의 함수로 만들어요
  • Unix 파이프라인 (|) 같은 거예요!

예제 5: all과 any - 조건 체크의 달인!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# all: 모든 요소가 True인지
numbers = [2, 4, 6, 8, 10]
all_even = all(map(lambda x: x % 2 == 0, numbers))
print(all_even)  # True

numbers = [2, 4, 5, 8, 10]
all_even = all(map(lambda x: x % 2 == 0, numbers))
print(all_even)  # False

# any: 하나라도 True인지
numbers = [1, 3, 5, 7, 8]
has_even = any(map(lambda x: x % 2 == 0, numbers))
print(has_even)  # True

# 실전: 유효성 검사
data = {
    'name': 'Alice',
    'age': 25,
    'email': '[email protected]'
}

required_fields = ['name', 'age', 'email']
is_valid = all(map(lambda field: field in data, required_fields))
print(f"데이터 유효: {is_valid}")  # True

all vs any! 🔍

  • all(): 모든 게 True여야 True (AND)
  • any(): 하나라도 True면 True (OR)

💡 오늘의 핵심 요약

1. map - 변환

1
map(lambda x: x * 2, [1, 2, 3])  # [2, 4, 6]

2. filter - 필터링

1
filter(lambda x: x > 0, [-1, 0, 1, 2])  # [1, 2]

3. reduce - 축약

1
reduce(lambda acc, x: acc + x, [1, 2, 3])  # 6

4. 람다 - 익명 함수

1
lambda x, y: x + y

5. 함수형 원칙

  • 순수 함수: 부작용 없이
  • 불변성: 데이터 변경 안 함
  • 함수 합성: 함수를 조립

🧪 연습 문제

문제 1: 학생 성적 처리

학생들의 점수 리스트에서 80점 이상인 학생들의 평균을 구하세요.

1
2
scores = [75, 82, 90, 68, 95, 88, 72, 91]
# 80점 이상의 평균: ?
💡 힌트

단계별 힌트:

  1. filter로 80점 이상만 골라내요
  2. sum()으로 합계를 구해요
  3. len()으로 개수를 세요
  4. 합계 ÷ 개수 = 평균

핵심 키워드: filter(lambda x: x >= 80, ...), sum(), len()

정답 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from functools import reduce

scores = [75, 82, 90, 68, 95, 88, 72, 91]

# 80점 이상 필터링
high_scores = list(filter(lambda x: x >= 80, scores))

# 평균 계산
average = reduce(lambda acc, x: acc + x, high_scores) / len(high_scores)

print(f"80점 이상 평균: {average:.2f}")  # 89.20

# 또는 한 줄로 (더 간단)
high_scores = list(filter(lambda x: x >= 80, scores))
average = sum(high_scores) / len(high_scores)
print(f"80점 이상 평균: {average:.2f}")

출력:

1
80점 이상 평균: 89.20

설명:

  • 80점 이상: [82, 90, 95, 88, 91]
  • 합계: 446
  • 평균: 446 ÷ 5 = 89.20

📝 오늘 배운 내용 정리

  1. 함수형 프로그래밍: 함수를 조립해서 데이터를 처리하는 방식이에요
  2. map: 모든 요소에 함수를 적용해요 (변환)
  3. filter: 조건에 맞는 요소만 골라내요 (필터링)
  4. reduce: 여러 값을 하나로 합쳐요 (축약)
  5. 람다: 간단한 익명 함수를 빠르게 만들어요

🔗 관련 자료


📚 이전 학습

Day 65: 컨텍스트 매니저 ⭐⭐⭐

어제는 with 문과 컨텍스트 매니저로 리소스를 자동 관리하는 방법을 배웠어요!

📚 다음 학습

Day 67: 함수형 프로그래밍 심화 ⭐⭐⭐

내일은 functools 모듈, 파셜 함수, 메모이제이션 등 고급 함수형 프로그래밍 기법을 배워요!


“작은 함수들을 조립하면 강력한 프로그램이 돼요!” 🚀

Day 66/100 Phase 7: 고급 파이썬 개념 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.