포스트

[Python 100일 챌린지] Day 28 - 람다 함수 (lambda)

[Python 100일 챌린지] Day 28 - 람다 함수 (lambda)

한 줄로 간단한 함수를 만드는 람다! 간결하고 강력한 익명 함수의 세계로 빠져봅시다. (30분 완독 ⭐⭐⭐)

🎯 오늘의 학습 목표

  1. 람다 함수의 개념 이해하기
  2. 람다 표현식 작성하기
  3. sorted()와 map(), filter() 마스터하기
  4. 람다 조합 패턴 익히기
  5. 람다 실전 활용과 주의사항

📚 사전 지식

🎯 학습 목표 1: 람다 함수의 개념 이해하기

한 줄 설명

람다 함수 = 이름이 없는 간단한 함수 (익명 함수, Anonymous Function)

한 줄 표현식으로 함수를 정의할 수 있는 간결한 문법입니다.

실생활 비유

1
2
3
4
5
6
7
8
9
🏪 일반 가게 (def 함수):
- 간판이 있음 (이름)
- 여러 상품 판매 가능 (여러 줄 코드)
- 언제든 다시 방문 가능 (재사용)

🛒 팝업 스토어 (lambda 함수):
- 간판 없음 (익명)
- 한 가지만 판매 (한 줄 표현식)
- 임시로 사용 (일회성)

왜 람다 함수를 사용할까?

1. 간결성 (Conciseness)

1
2
3
4
5
6
7
8
# def 함수: 4줄
def double(x):
    return x * 2

result = double(5)  # 10

# lambda: 1줄
result = (lambda x: x * 2)(5)  # 10

2. 일회성 함수 (Disposable Functions)

1
2
3
4
5
6
7
8
9
10
11
# 정렬할 때 한 번만 사용하는 함수
students = [("홍길동", 85), ("김철수", 92), ("이영희", 78)]

# def 함수 방식
def get_score(student):
    return student[1]

sorted_students = sorted(students, key=get_score)

# lambda 방식 (더 간결)
sorted_students = sorted(students, key=lambda s: s[1])

3. 고차 함수와의 조합 (Higher-Order Functions)

람다는 map(), filter(), sorted() 등 다른 함수의 인자로 전달될 때 매우 유용합니다.

1
2
3
4
5
6
7
8
9
numbers = [1, 2, 3, 4, 5]

# map과 lambda: 모든 수를 제곱
squares = list(map(lambda x: x ** 2, numbers))
print(squares)  # [1, 4, 9, 16, 25]

# filter와 lambda: 짝수만 선택
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4]

람다의 역사와 유래

람다(λ) 는 그리스 문자로, 1930년대 수학자 Alonzo Church가 개발한 람다 대수(Lambda Calculus) 에서 유래했습니다.

1
2
3
4
5
수학적 표현:
λx.x + 1  →  "x를 받아서 x+1을 반환하는 함수"

Python 표현:
lambda x: x + 1

람다 대수는 현대 함수형 프로그래밍의 기초가 되었고, Python뿐만 아니라 JavaScript, Java, C# 등 많은 언어가 이 개념을 채택했습니다.

람다 함수의 특징 정리

특징 설명 예시
익명성 이름이 없음 (변수에 할당 가능) f = lambda x: x + 1
단일 표현식 한 줄의 표현식만 가능 lambda x: x ** 2
암묵적 반환 return 키워드 불필요 lambda x, y: x + y
일회성 주로 한 번만 사용 sorted(lst, key=lambda x: x[1])
제한적 여러 문장, 할당문 불가 lambda x: y = x + 1

🎯 학습 목표 2: lambda 문법 익히기

기본 문법 구조

1
lambda 매개변수: 표현식

구성 요소:

  1. lambda 키워드로 시작
  2. 매개변수는 콤마(,)로 구분
  3. 콜론(:) 뒤에 단일 표현식
  4. return 키워드 불필요 (자동으로 반환)

def vs lambda 상세 비교

예제 1: 단일 인자

1
2
3
4
5
6
7
8
9
10
11
12
13
# ✅ 일반 함수 (def)
def square(x):
    return x ** 2

print(square(5))  # 25

# ✅ 람다 함수 (lambda)
square_lambda = lambda x: x ** 2

print(square_lambda(5))  # 25

# ✅ 람다를 변수에 할당하지 않고 바로 호출
print((lambda x: x ** 2)(5))  # 25

예제 2: 다중 인자

1
2
3
4
5
6
7
8
9
10
# def 함수
def add(x, y):
    return x + y

print(add(3, 5))  # 8

# lambda 함수
add_lambda = lambda x, y: x + y

print(add_lambda(3, 5))  # 8

예제 3: 복잡한 표현식

1
2
3
4
5
6
7
8
9
10
# def 함수
def calculate(x, y, z):
    return (x + y) * z

print(calculate(2, 3, 4))  # 20

# lambda 함수
calculate_lambda = lambda x, y, z: (x + y) * z

print(calculate_lambda(2, 3, 4))  # 20

문법 비교표

특징 def 함수 lambda 함수
이름 필수 선택 (변수에 할당 가능)
줄 수 여러 줄 가능 단 한 줄만
return 명시적 return 암묵적 반환
문서화 Docstring 가능 불가능
재사용 언제든지 호출 주로 일회성
디버깅 함수 이름으로 추적 익명이라 어려움

람다 함수 작성 방법

방법 1: 변수에 할당

1
2
3
4
5
# 람다를 변수에 저장
multiply = lambda x, y: x * y

print(multiply(3, 4))  # 12
print(multiply(5, 6))  # 30

언제 사용: 람다를 여러 번 재사용해야 할 때 (하지만 이런 경우 def를 쓰는 게 더 좋습니다)

방법 2: 즉시 실행

1
2
3
# 람다를 정의하고 바로 호출
result = (lambda x, y: x + y)(10, 20)
print(result)  # 30

언제 사용: 딱 한 번만 사용할 계산 (매우 드뭅니다)

방법 3: 고차 함수의 인자 (가장 일반적)

1
2
3
4
5
6
# sorted의 key 인자로 전달
students = [("홍길동", 85), ("김철수", 92), ("이영희", 78)]

sorted_students = sorted(students, key=lambda s: s[1])
print(sorted_students)
# [('이영희', 78), ('홍길동', 85), ('김철수', 92)]

언제 사용: map(), filter(), sorted(), max(), min() 등과 함께 (가장 권장되는 사용법!)

매개변수 패턴

1. 매개변수 없음

1
2
3
4
# 매개변수 없이도 정의 가능
get_pi = lambda: 3.14159

print(get_pi())  # 3.14159

2. 단일 매개변수

1
2
3
4
# 가장 기본적인 형태
square = lambda x: x ** 2

print(square(7))  # 49

3. 다중 매개변수

1
2
3
4
# 여러 매개변수 (콤마로 구분)
add_three = lambda a, b, c: a + b + c

print(add_three(1, 2, 3))  # 6

4. 기본값이 있는 매개변수

1
2
3
4
5
# 기본값 지정 가능
greet = lambda name, greeting="안녕하세요": f"{greeting}, {name}님!"

print(greet("홍길동"))  # 안녕하세요, 홍길동님!
print(greet("김철수", "환영합니다"))  # 환영합니다, 김철수님!

조건 표현식과 람다

람다 안에서 if-else를 사용할 수 있습니다 (삼항 연산자).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 절댓값 계산
abs_value = lambda x: x if x >= 0 else -x

print(abs_value(5))   # 5
print(abs_value(-5))  # 5

# 짝수/홀수 판별
is_even = lambda x: "짝수" if x % 2 == 0 else "홀수"

print(is_even(4))  # 짝수
print(is_even(7))  # 홀수

# 최댓값 찾기
max_value = lambda a, b: a if a > b else b

print(max_value(10, 20))  # 20

주의: if-else표현식(값을 반환)이므로 가능하지만, if 문(Statement)은 불가능합니다.

1
2
3
4
5
# ❌ 이런 건 불가능 (if 문은 값을 반환하지 않음)
# check = lambda x: if x > 0: return "양수"  # SyntaxError

# ✅ 삼항 연산자 사용
check = lambda x: "양수" if x > 0 else "음수 또는 0"

💡 람다 함수 예제

예제 1: 간단한 연산

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 제곱
square = lambda x: x ** 2
print(square(5))  # 25

# 곱하기
multiply = lambda x, y: x * y
print(multiply(3, 4))  # 12

# 세 수의 합
sum_three = lambda a, b, c: a + b + c
print(sum_three(1, 2, 3))  # 6

# 조건 표현식
max_two = lambda a, b: a if a > b else b
print(max_two(10, 20))  # 20

예제 2: 문자열 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 문자열 길이
length = lambda s: len(s)
print(length("Python"))  # 6

# 대문자 변환
to_upper = lambda s: s.upper()
print(to_upper("hello"))  # HELLO

# 문자열 결합
concat = lambda s1, s2: s1 + " " + s2
print(concat("Hello", "World"))  # Hello World

# 첫 글자 추출
first_char = lambda s: s[0] if s else ""
print(first_char("Python"))  # P
print(first_char(""))  # (빈 문자열)

예제 3: 리스트 처리

1
2
3
4
5
6
7
8
9
10
11
# 리스트 길이
list_len = lambda lst: len(lst)
print(list_len([1, 2, 3, 4, 5]))  # 5

# 리스트 합계
list_sum = lambda lst: sum(lst)
print(list_sum([1, 2, 3, 4, 5]))  # 15

# 리스트 평균
list_avg = lambda lst: sum(lst) / len(lst) if lst else 0
print(list_avg([10, 20, 30]))  # 20.0

🎯 학습 목표 3: sorted()와 map(), filter() 마스터하기

sorted() - 기본 정렬

1
2
3
4
5
6
7
8
9
numbers = [5, 2, 8, 1, 9]

# 오름차순 (기본)
sorted_asc = sorted(numbers)
print(sorted_asc)  # [1, 2, 5, 8, 9]

# 내림차순
sorted_desc = sorted(numbers, reverse=True)
print(sorted_desc)  # [9, 8, 5, 2, 1]

복잡한 정렬 (key 사용)

1
2
3
4
5
6
# 문자열 길이로 정렬
words = ["Python", "is", "awesome", "and", "powerful"]

sorted_words = sorted(words, key=lambda w: len(w))
print(sorted_words)
# ['is', 'and', 'Python', 'awesome', 'powerful']

튜플 리스트 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 학생 (이름, 점수) 튜플
students = [
    ("홍길동", 85),
    ("김철수", 92),
    ("이영희", 78),
    ("박민수", 88)
]

# 점수로 정렬 (오름차순)
sorted_by_score = sorted(students, key=lambda s: s[1])
print(sorted_by_score)
# [('이영희', 78), ('홍길동', 85), ('박민수', 88), ('김철수', 92)]

# 이름으로 정렬
sorted_by_name = sorted(students, key=lambda s: s[0])
print(sorted_by_name)
# [('김철수', 92), ('박민수', 88), ('이영희', 78), ('홍길동', 85)]

딕셔너리 리스트 정렬

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
students = [
    {"name": "홍길동", "age": 25, "score": 85},
    {"name": "김철수", "age": 22, "score": 92},
    {"name": "이영희", "age": 24, "score": 78},
    {"name": "박민수", "age": 23, "score": 88}
]

# 점수로 정렬
sorted_by_score = sorted(students, key=lambda s: s["score"])
for student in sorted_by_score:
    print(f"{student['name']}: {student['score']}")
# 이영희: 78점
# 홍길동: 85점
# 박민수: 88점
# 김철수: 92점

# 나이로 정렬
sorted_by_age = sorted(students, key=lambda s: s["age"])

# 이름으로 정렬
sorted_by_name = sorted(students, key=lambda s: s["name"])

복합 정렬

1
2
3
4
5
# 나이순, 같으면 점수순
sorted_complex = sorted(students, key=lambda s: (s["age"], s["score"]))

# 점수 내림차순
sorted_desc = sorted(students, key=lambda s: s["score"], reverse=True)

map() 기본

1
2
3
4
5
6
7
8
9
10
# 기본 사용법
numbers = [1, 2, 3, 4, 5]

# 제곱
squares = list(map(lambda x: x ** 2, numbers))
print(squares)  # [1, 4, 9, 16, 25]

# 두 배
doubles = list(map(lambda x: x * 2, numbers))
print(doubles)  # [2, 4, 6, 8, 10]

문자열 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
words = ["python", "java", "javascript", "c++"]

# 대문자 변환
upper_words = list(map(lambda w: w.upper(), words))
print(upper_words)
# ['PYTHON', 'JAVA', 'JAVASCRIPT', 'C++']

# 길이 계산
lengths = list(map(lambda w: len(w), words))
print(lengths)  # [6, 4, 10, 3]

# 첫 글자 추출
first_letters = list(map(lambda w: w[0], words))
print(first_letters)  # ['p', 'j', 'j', 'c']

여러 리스트 동시 처리

1
2
3
4
5
6
7
8
9
10
11
# 두 리스트의 같은 위치 요소끼리 계산
numbers1 = [1, 2, 3, 4, 5]
numbers2 = [10, 20, 30, 40, 50]

# 덧셈
sums = list(map(lambda x, y: x + y, numbers1, numbers2))
print(sums)  # [11, 22, 33, 44, 55]

# 곱셈
products = list(map(lambda x, y: x * y, numbers1, numbers2))
print(products)  # [10, 40, 90, 160, 250]

딕셔너리 변환

1
2
3
4
5
6
7
names = ["홍길동", "김철수", "이영희"]
scores = [85, 92, 78]

# 이름과 점수를 딕셔너리로
students = list(map(lambda n, s: {"name": n, "score": s}, names, scores))
print(students)
# [{'name': '홍길동', 'score': 85}, {'name': '김철수', 'score': 92}, {'name': '이영희', 'score': 78}]

filter() - 조건 필터링

1
2
3
4
5
6
7
8
9
10
11
12
13
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 짝수만
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4, 6, 8, 10]

# 홀수만
odds = list(filter(lambda x: x % 2 == 1, numbers))
print(odds)  # [1, 3, 5, 7, 9]

# 5보다 큰 수
greater_than_5 = list(filter(lambda x: x > 5, numbers))
print(greater_than_5)  # [6, 7, 8, 9, 10]

문자열 필터링

1
2
3
4
5
6
7
8
9
words = ["Python", "is", "awesome", "and", "powerful"]

# 길이가 5 이상인 단어
long_words = list(filter(lambda w: len(w) >= 5, words))
print(long_words)  # ['Python', 'awesome', 'powerful']

# 'a'로 시작하는 단어
a_words = list(filter(lambda w: w.startswith('a'), words))
print(a_words)  # ['awesome', 'and']

딕셔너리 필터링

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
students = [
    {"name": "홍길동", "score": 85},
    {"name": "김철수", "score": 92},
    {"name": "이영희", "score": 78},
    {"name": "박민수", "score": 88}
]

# 80점 이상
passed = list(filter(lambda s: s["score"] >= 80, students))
print(passed)
# [{'name': '홍길동', 'score': 85}, {'name': '김철수', 'score': 92}, {'name': '박민수', 'score': 88}]

# 90점 이상 (우등생)
excellent = list(filter(lambda s: s["score"] >= 90, students))
print(excellent)
# [{'name': '김철수', 'score': 92}]

🎯 학습 목표 4: 람다 조합 패턴 익히기

map + filter 조합

1
2
3
4
5
6
7
8
9
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 짝수만 골라서 제곱
even_squares = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers)))
print(even_squares)  # [4, 16, 36, 64, 100]

# 5보다 큰 수만 두 배
doubled_greater = list(map(lambda x: x * 2, filter(lambda x: x > 5, numbers)))
print(doubled_greater)  # [12, 14, 16, 18, 20]

sorted + map 조합

1
2
3
4
5
6
words = ["Python", "is", "awesome", "and", "powerful"]

# 길이순으로 정렬 후 대문자 변환
sorted_upper = list(map(lambda w: w.upper(), sorted(words, key=lambda w: len(w))))
print(sorted_upper)
# ['IS', 'AND', 'PYTHON', 'AWESOME', 'POWERFUL']

종합 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
students = [
    {"name": "홍길동", "age": 25, "score": 85},
    {"name": "김철수", "age": 22, "score": 92},
    {"name": "이영희", "age": 24, "score": 78},
    {"name": "박민수", "age": 23, "score": 88},
    {"name": "최영희", "age": 26, "score": 95}
]

# 80점 이상 학생만 골라서, 점수순 정렬 후, 이름만 추출
top_students = list(map(
    lambda s: s["name"],
    sorted(
        filter(lambda s: s["score"] >= 80, students),
        key=lambda s: s["score"],
        reverse=True
    )
))

print(top_students)
# ['최영희', '김철수', '박민수', '홍길동']

🎯 학습 목표 5: 람다 실전 활용과 주의사항

람다가 빛나는 상황

상황 1: 정렬 키 함수

1
2
3
4
5
6
# 파일명을 확장자별로 정렬
files = ["report.pdf", "data.csv", "image.png", "script.py", "readme.md"]

sorted_files = sorted(files, key=lambda f: f.split('.')[-1])
print(sorted_files)
# ['data.csv', 'readme.md', 'report.pdf', 'image.png', 'script.py']

상황 2: 데이터 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# API 응답 데이터 변환
api_data = [
    {"user_id": 1, "user_name": "홍길동", "user_age": 25},
    {"user_id": 2, "user_name": "김철수", "user_age": 30}
]

# 키 이름 간소화
simplified = list(map(lambda d: {
    "id": d["user_id"],
    "name": d["user_name"],
    "age": d["user_age"]
}, api_data))

print(simplified)
# [{'id': 1, 'name': '홍길동', 'age': 25}, {'id': 2, 'name': '김철수', 'age': 30}]

상황 3: 조건부 변환

1
2
3
4
5
6
# 숫자 리스트를 양수/음수 표시로 변환
numbers = [-5, 3, -2, 7, 0, -1, 4]

signs = list(map(lambda x: "+" if x > 0 else ("-" if x < 0 else "0"), numbers))
print(signs)
# ['-', '+', '-', '+', '0', '-', '+']

람다의 한계와 주의사항

주의 1: 한 줄만 가능

1
2
3
4
5
6
7
8
9
10
11
# ❌ 여러 줄 불가능
# result = lambda x:
#     if x > 0:
#         return x
#     else:
#         return -x

# ✅ 조건 표현식 사용
abs_value = lambda x: x if x > 0 else -x
print(abs_value(-5))  # 5
print(abs_value(3))   # 3

주의 2: 복잡한 로직은 def 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ❌ 람다로 하기에는 너무 복잡
# grade = lambda score: "A" if score >= 90 else "B" if score >= 80 else "C" if score >= 70 else "F"

# ✅ def 함수 사용
def get_grade(score):
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

print(get_grade(85))  # B

주의 3: 가독성 우선

1
2
3
4
5
6
7
8
# ❌ 읽기 어려운 람다
result = list(map(lambda x: x ** 2 if x % 2 == 0 else x ** 3, filter(lambda x: x > 5, range(20))))

# ✅ 명확한 코드
numbers = range(20)
greater_than_5 = filter(lambda x: x > 5, numbers)
transformed = map(lambda x: x ** 2 if x % 2 == 0 else x ** 3, greater_than_5)
result = list(transformed)

주의 4: 디버깅 어려움

1
2
3
4
5
6
7
8
9
10
# 람다는 이름이 없어서 에러 추적 어려움
# def 함수는 함수 이름으로 에러 위치 파악 가능

# ✅ 중요한 로직은 def 사용
def calculate_discount(price):
    return price * 0.9

# ✅ 간단한 일회성 연산은 람다 OK
prices = [100, 200, 300]
discounted = list(map(lambda p: p * 0.9, prices))

트러블슈팅

문제 1: 람다 안에서 변수 수정

증상:

1
2
# ❌ 람다는 표현식만 가능, 할당문 불가
# result = lambda x: count += 1  # SyntaxError

해결:

1
2
3
4
5
6
# ✅ def 함수 사용
count = 0
def increment():
    global count
    count += 1
    return count

문제 2: 람다 참조 오류

증상:

1
2
3
4
5
6
7
# ❌ 예상과 다른 결과
functions = []
for i in range(3):
    functions.append(lambda: i)

# 모두 2를 반환 (마지막 i 값)
print([f() for f in functions])  # [2, 2, 2]

해결:

1
2
3
4
5
6
# ✅ 기본값으로 값 캡처
functions = []
for i in range(3):
    functions.append(lambda x=i: x)

print([f() for f in functions])  # [0, 1, 2]

🧪 연습 문제

문제 1: 온도 변환기

람다 함수를 사용해서 섭씨를 화씨로 변환하세요. (화씨 = 섭씨 * 9/5 + 32)

💡 힌트
1
celsius_to_fahrenheit = lambda c: # 공식 적용
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 람다 함수 정의
celsius_to_fahrenheit = lambda c: c * 9/5 + 32

# 테스트
celsius_temps = [0, 10, 20, 30, 37, 100]
fahrenheit_temps = list(map(celsius_to_fahrenheit, celsius_temps))

for c, f in zip(celsius_temps, fahrenheit_temps):
    print(f"{c}°C = {f:.1f}°F")

# 0°C = 32.0°F
# 10°C = 50.0°F
# 20°C = 68.0°F
# 30°C = 86.0°F
# 37°C = 98.6°F
# 100°C = 212.0°F

문제 2: 이름 필터링

다음 리스트에서 성이 “김”인 사람만 필터링하세요.

1
names = ["홍길동", "김철수", "이영희", "김민지", "박민수", "김영수"]
💡 힌트

filter()startswith() 사용

1
kim_names = list(filter(lambda name: name.startswith(""), names))
✅ 정답
1
2
3
4
5
6
7
8
9
10
11
names = ["홍길동", "김철수", "이영희", "김민지", "박민수", "김영수"]

# 성이 "김"인 사람만
kim_names = list(filter(lambda name: name.startswith(""), names))
print(kim_names)
# ['김철수', '김민지', '김영수']

# 이름 길이가 3글자인 사람
three_char_names = list(filter(lambda name: len(name) == 3, names))
print(three_char_names)
# ['홍길동', '김철수', '이영희', '박민수', '김영수']

문제 3: 성적 등급 계산

학생 리스트를 점수순으로 정렬하고, 90점 이상만 이름을 추출하세요.

1
2
3
4
5
6
7
students = [
    {"name": "홍길동", "score": 85},
    {"name": "김철수", "score": 92},
    {"name": "이영희", "score": 78},
    {"name": "박민수", "score": 95},
    {"name": "최영희", "score": 88}
]
💡 힌트
  1. filter()로 90점 이상만
  2. sorted()로 점수순 정렬
  3. map()으로 이름만 추출
✅ 정답
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
30
31
students = [
    {"name": "홍길동", "score": 85},
    {"name": "김철수", "score": 92},
    {"name": "이영희", "score": 78},
    {"name": "박민수", "score": 95},
    {"name": "최영희", "score": 88}
]

# 90점 이상 학생 필터링
excellent_students = filter(lambda s: s["score"] >= 90, students)

# 점수순 정렬 (내림차순)
sorted_students = sorted(excellent_students, key=lambda s: s["score"], reverse=True)

# 이름만 추출
names = list(map(lambda s: s["name"], sorted_students))

print(names)
# ['박민수', '김철수']

# 한 줄로 작성
names_oneline = list(map(
    lambda s: s["name"],
    sorted(
        filter(lambda s: s["score"] >= 90, students),
        key=lambda s: s["score"],
        reverse=True
    )
))
print(names_oneline)
# ['박민수', '김철수']

❓ 자주 묻는 질문 (FAQ)

Q1. 람다 함수와 일반 함수(def)는 언제 구분해서 사용하나요?

람다 함수를 사용할 때:

  • sorted(), map(), filter() 등의 고차 함수에 간단한 로직을 전달할 때
  • 일회용으로 사용되는 간단한 함수가 필요할 때
  • 함수 로직이 한 줄로 표현 가능할 때
1
2
3
4
5
6
7
# 람다가 적절한 경우
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))

# 정렬 키로 사용
students = [{"name": "김철수", "score": 85}, {"name": "이영희", "score": 92}]
sorted_students = sorted(students, key=lambda s: s["score"])

일반 함수(def)를 사용할 때:

  • 함수에 이름을 부여하고 여러 곳에서 재사용할 때
  • 복잡한 로직이 필요할 때
  • 여러 줄의 코드가 필요할 때
  • 디버깅이 중요한 경우
1
2
3
4
5
6
7
8
9
10
# def가 적절한 경우
def validate_email(email):
    """이메일 형식을 검증하는 함수"""
    if not email:
        return False
    if "@" not in email:
        return False
    if "." not in email.split("@")[1]:
        return False
    return True
Q2. 람다 함수 안에서 왜 변수를 수정할 수 없나요?

람다 함수는 표현식(expression)만 사용 가능하고, 문장(statement)은 사용할 수 없기 때문입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ❌ 불가능: 할당문은 statement
# lambda x: y = x + 1  # SyntaxError

# ❌ 불가능: 여러 줄의 코드
# lambda x: (
#     result = x * 2
#     return result
# )

# ✅ 가능: 단일 표현식
lambda x: x * 2

# ✅ 가능: 조건 표현식 (삼항 연산자)
lambda x: "양수" if x > 0 else "음수 또는 0"

# ✅ 가능: 여러 표현식을 튜플로 반환
lambda x, y: (x + y, x - y, x * y)

해결 방법: 복잡한 로직이 필요하면 def 함수를 사용하세요.

1
2
3
4
5
def complex_operation(x):
    result = x * 2
    if result > 100:
        result = 100
    return result
Q3. 람다 함수의 성능이 일반 함수보다 느린가요?

아니오, 람다 함수와 일반 함수의 성능은 거의 동일합니다.

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
import time

# 람다 함수
lambda_square = lambda x: x ** 2

# 일반 함수
def def_square(x):
    return x ** 2

# 성능 테스트
numbers = list(range(1000000))

# 람다 테스트
start = time.time()
result1 = list(map(lambda x: x ** 2, numbers))
lambda_time = time.time() - start

# def 테스트
start = time.time()
result2 = list(map(def_square, numbers))
def_time = time.time() - start

print(f"람다: {lambda_time:.4f}")
print(f"def: {def_time:.4f}")
# 차이는 거의 없음 (0.001초 미만)

성능보다 중요한 것:

  • 가독성: 코드를 읽는 사람이 이해하기 쉬운가?
  • 유지보수성: 나중에 수정하기 쉬운가?
  • 디버깅: 문제가 생겼을 때 찾기 쉬운가?
Q4. 람다 함수를 변수에 할당하면 안 되나요?

PEP 8 스타일 가이드에서는 권장하지 않습니다.

1
2
3
4
5
6
7
8
9
10
# ❌ PEP 8 위반: 람다를 변수에 할당
square = lambda x: x ** 2
add = lambda x, y: x + y

# ✅ 권장: def 함수 사용
def square(x):
    return x ** 2

def add(x, y):
    return x + y

이유:

  1. 디버깅 어려움: 에러 메시지에서 함수 이름이 <lambda>로 표시됨
  2. 가독성 저하: 함수의 목적이 명확하지 않음
  3. 문서화 불가: docstring을 추가할 수 없음
1
2
3
4
5
6
7
# 람다의 디버깅 문제
square = lambda x: x / 0  # 일부러 에러 발생
try:
    square(5)
except Exception as e:
    print(e)  # division by zero
    # 추적 정보에서 함수 이름이 <lambda>로 나타남

람다는 일회용으로 사용하세요:

1
2
3
4
5
6
# ✅ 좋은 예: 일회용으로 사용
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))

# ✅ 좋은 예: 정렬 키로 사용
students.sort(key=lambda s: s["score"])
Q5. 람다 함수 안에서 람다 함수를 사용할 수 있나요?

네, 가능하지만 가독성이 매우 떨어지므로 권장하지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ❌ 중첩 람다 (읽기 어려움)
nested = lambda x: (lambda y: x + y)
result = nested(10)(5)  # 15

# ✅ def 함수로 명확하게
def outer(x):
    def inner(y):
        return x + y
    return inner

result = outer(10)(5)  # 15

# 또는 클로저 패턴
def make_adder(x):
    """x를 더하는 함수를 반환"""
    return lambda y: x + y

add_10 = make_adder(10)
print(add_10(5))  # 15
print(add_10(20))  # 30

중첩 람다가 허용되는 경우:

  • 함수형 프로그래밍 패턴에서 간단한 커링(currying)을 구현할 때
  • 하지만 여전히 def 함수가 더 명확함
Q6. map()과 filter() 대신 리스트 컴프리헨션을 사용해도 되나요?

네, 대부분의 경우 리스트 컴프리헨션이 더 파이썬스럽고 가독성이 좋습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
numbers = [1, 2, 3, 4, 5]

# map() 사용
squared_map = list(map(lambda x: x ** 2, numbers))

# 리스트 컴프리헨션 (더 명확함)
squared_comp = [x ** 2 for x in numbers]

# filter() 사용
even_filter = list(filter(lambda x: x % 2 == 0, numbers))

# 리스트 컴프리헨션 (더 명확함)
even_comp = [x for x in numbers if x % 2 == 0]

# map() + filter() 조합
result_map = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numbers)))

# 리스트 컴프리헨션 (훨씬 읽기 쉬움)
result_comp = [x ** 2 for x in numbers if x % 2 == 0]

언제 map()/filter()를 사용하나요?

  1. 이미 정의된 함수를 사용할 때:
    1
    2
    3
    4
    5
    6
    
    # map()이 더 간결함
    words = ["hello", "world", "python"]
    upper_words = list(map(str.upper, words))
    
    # 컴프리헨션
    upper_words = [word.upper() for word in words]
    
  2. 함수형 프로그래밍 스타일을 선호할 때
  3. lazy evaluation이 필요할 때 (리스트로 변환하지 않고 이터레이터로 사용)

PEP 8 권장사항: 대부분의 경우 리스트 컴프리헨션이 더 파이썬스럽습니다.

Q7. 람다 함수에서 여러 개의 값을 반환할 수 있나요?

네, 튜플을 사용하면 여러 값을 반환할 수 있습니다.

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
# 두 수의 합과 곱을 동시에 반환
calc = lambda x, y: (x + y, x * y)

sum_result, mul_result = calc(3, 4)
print(f"합: {sum_result}, 곱: {mul_result}")  # 합: 7, 곱: 12

# 실전 예제: 학생 정보에서 이름과 등급 추출
students = [
    {"name": "김철수", "score": 92},
    {"name": "이영희", "score": 78},
    {"name": "박민수", "score": 95}
]

# 이름과 등급을 튜플로 반환
name_grade = list(map(
    lambda s: (s["name"], "A" if s["score"] >= 90 else "B"),
    students
))

print(name_grade)
# [('김철수', 'A'), ('이영희', 'B'), ('박민수', 'A')]

# 딕셔너리로 반환하는 것도 가능
student_info = list(map(
    lambda s: {"name": s["name"], "grade": "A" if s["score"] >= 90 else "B"},
    students
))

주의사항: 반환값이 복잡해지면 def 함수를 사용하는 것이 좋습니다.

Q8. 람다 함수는 재귀적으로 호출할 수 있나요?

이론적으로는 가능하지만 실용적이지 않고 권장하지 않습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ❌ 람다 재귀 (가능하지만 복잡함)
factorial = lambda n: 1 if n == 0 else n * factorial(n - 1)
print(factorial(5))  # 120

# ⚠️ 문제: factorial이 정의되기 전에 참조됨 (순환 참조)

# ✅ Y-combinator를 사용한 람다 재귀 (매우 복잡)
Y = lambda f: (lambda x: f(lambda *args: x(x)(*args)))(lambda x: f(lambda *args: x(x)(*args)))
factorial_y = Y(lambda f: lambda n: 1 if n == 0 else n * f(n - 1))
print(factorial_y(5))  # 120

# ✅✅ 권장: def 함수 사용 (명확하고 간단함)
def factorial_def(n):
    """팩토리얼을 계산하는 재귀 함수"""
    if n == 0:
        return 1
    return n * factorial_def(n - 1)

print(factorial_def(5))  # 120

결론: 재귀가 필요하면 def 함수를 사용하세요. 람다 재귀는 학술적 흥미는 있지만 실무에서는 사용하지 않습니다.


💡 실전 팁 & 주의사항

1. 람다는 변수에 할당하지 말것 (PEP 8)

1
2
3
4
5
6
# ❌ 나쁜 예: 람다를 변수에 할당
square = lambda x: x ** 2

# ✅ 좋은 예: def 함수 사용
def square(x):
    return x ** 2

이유: PEP 8 스타일 가이드에서 람다를 변수에 할당하는 것을 권장하지 않습니다. 함수 이름이 필요하다면 def를 사용하세요.

2. 가독성이 우선

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# ❌ 읽기 어려운 중첩 람다
result = list(map(lambda x: x ** 2 if x % 2 == 0 else x ** 3,
                  filter(lambda x: x > 5, range(20))))

# ✅ 명확한 def 함수
def transform(x):
    return x ** 2 if x % 2 == 0 else x ** 3

numbers = [x for x in range(20) if x > 5]
result = list(map(transform, numbers))

# ✅ 또는 리스트 컴프리헨션 (더 Pythonic)
result = [x ** 2 if x % 2 == 0 else x ** 3
          for x in range(20) if x > 5]

이유: 코드를 읽는 사람(미래의 나 포함)을 위해 명확한 코드가 더 중요합니다.

3. 리스트 컴프리헨션을 고려하기

1
2
3
4
5
6
7
8
9
10
11
12
13
numbers = [1, 2, 3, 4, 5]

# ❌ map + lambda
squares = list(map(lambda x: x ** 2, numbers))

# ✅ 리스트 컴프리헨션 (더 Pythonic)
squares = [x ** 2 for x in numbers]

# ❌ filter + lambda
evens = list(filter(lambda x: x % 2 == 0, numbers))

# ✅ 리스트 컴프리헨션
evens = [x for x in numbers if x % 2 == 0]

이유: 대부분의 경우 리스트 컴프리헨션이 더 읽기 쉽고 Python다운 코드입니다.

4. 람다가 빛나는 상황

1
2
3
4
5
6
7
8
9
# ✅ sorted의 key 함수로 사용 (매우 적절!)
students = [("홍길동", 85), ("김철수", 92), ("이영희", 78)]
sorted_students = sorted(students, key=lambda s: s[1])

# ✅ max/min의 key 함수로 사용
youngest = min(students, key=lambda s: s[1])

# ✅ 콜백 함수로 사용 (GUI 프로그래밍)
button.on_click(lambda: print("버튼 클릭!"))

이유: 한 번만 사용할 간단한 키 함수나 콜백에는 람다가 완벽합니다.

5. 람다의 한계 인정하기

1
2
3
4
5
6
7
8
9
10
11
12
13
# ❌ 람다로는 불가능한 작업들
# - 여러 문장 (Statements)
# - 할당문 (Assignments)
# - 복잡한 로직

# ✅ def 함수 사용
def complex_function(x):
    if x < 0:
        return 0
    elif x > 100:
        return 100
    else:
        return x

이유: 람다는 단일 표현식만 가능합니다. 복잡한 로직에는 def를 사용하세요.

6. type() 으로 확인

1
2
3
4
f = lambda x: x + 1

print(type(f))  # <class 'function'>
print(f.__name__)  # <lambda>

이유: 람다도 함수입니다! 다만 이름이 <lambda>로 표시되어 디버깅이 어렵습니다.

7. 디버깅 어려움 인식하기

1
2
3
4
5
6
7
8
9
10
11
12
# ❌ 람다에서 에러 발생 시 추적이 어려움
data = [1, 2, "three", 4, 5]
result = list(map(lambda x: x * 2, data))
# TypeError: can't multiply sequence by non-int of type 'str'
# 에러 메시지에서 함수 이름이 <lambda>로만 표시됨

# ✅ def 함수는 함수 이름으로 에러 위치 파악 가능
def multiply_by_2(x):
    return x * 2

result = list(map(multiply_by_2, data))
# TypeError in multiply_by_2() - 명확한 위치 파악

: 디버깅이 중요한 작업에는 람다 대신 def 함수를 사용하세요.

8. 람다 체이닝 주의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# ❌ 람다 체이닝 (읽기 매우 어려움)
process = lambda data: list(map(
    lambda x: x ** 2,
    filter(lambda x: x % 2 == 0,
           map(lambda x: x + 1, data))
))

# ✅ 단계별 명확한 처리
def process_data(data):
    """데이터 처리 파이프라인"""
    # 1단계: 1 더하기
    step1 = [x + 1 for x in data]
    # 2단계: 짝수만 필터링
    step2 = [x for x in step1 if x % 2 == 0]
    # 3단계: 제곱
    step3 = [x ** 2 for x in step2]
    return step3

# ✅ 또는 리스트 컴프리헨션 (한 줄로 명확하게)
result = [x ** 2 for x in data if (x + 1) % 2 == 0]

: 2개 이상의 람다가 중첩되면 반드시 다른 방법을 고려하세요.

9. 기본 인자 활용하기

1
2
3
4
5
6
7
8
9
10
11
# 기본 인자를 활용한 람다
multiply = lambda x, y=2: x * y

print(multiply(5))     # 10 (y=2 기본값)
print(multiply(5, 3))  # 15 (y=3 지정)

# 실전 예제: 할인율 계산
apply_discount = lambda price, discount=0.1: price * (1 - discount)

print(apply_discount(10000))        # 9000원 (10% 할인)
print(apply_discount(10000, 0.2))   # 8000원 (20% 할인)

: 기본 인자를 사용하면 람다의 유연성이 높아집니다.

10. 튜플 언패킹 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 튜플을 받는 람다
points = [(1, 2), (3, 1), (5, 4), (2, 3)]

# ✅ 튜플 언패킹으로 명확하게
sorted_by_y = sorted(points, key=lambda p: p[1])

# ✅ 튜플 언패킹 활용 (더 명확함)
sorted_by_sum = sorted(points, key=lambda xy: xy[0] + xy[1])

# 실전 예제: 학생 정보 정렬
students = [
    ("김철수", 85, "서울"),
    ("이영희", 92, "부산"),
    ("박민수", 78, "대구")
]

# 이름, 점수, 지역을 활용한 다중 정렬
sorted_students = sorted(
    students,
    key=lambda student: (student[2], -student[1])  # 지역 오름차순, 점수 내림차순
)

: 인덱스보다는 명확한 변수명을 사용하면 가독성이 높아집니다.

11. None 반환 주의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# ❌ 람다가 None을 반환하는 경우 (의도하지 않은 동작)
numbers = [1, 2, 3, 4, 5]
result = list(map(lambda x: print(x), numbers))
print(result)  # [None, None, None, None, None]

# ✅ 값을 반환하도록 수정
result = list(map(lambda x: x * 2, numbers))
print(result)  # [2, 4, 6, 8, 10]

# 실전 예제: 부작용(side effect) 주의
data = []
list(map(lambda x: data.append(x * 2), range(5)))
# 동작은 하지만 권장하지 않음 (부작용 발생)

# ✅ 명확한 for 문 사용
data = []
for x in range(5):
    data.append(x * 2)

: 람다는 값을 반환하는 순수 함수로 사용하세요.

12. 조건부 표현식 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 삼항 연산자를 활용한 람다
is_adult = lambda age: "성인" if age >= 19 else "미성년자"

print(is_adult(25))  # 성인
print(is_adult(17))  # 미성년자

# 실전 예제: 등급 분류
grade = lambda score: "A" if score >= 90 else "B" if score >= 80 else "C"

students = [85, 92, 78, 95, 88]
grades = list(map(grade, students))
print(grades)  # ['B', 'A', 'C', 'A', 'B']

# 복잡한 조건은 def 사용 권장
def calculate_grade(score):
    """점수에 따른 등급 계산"""
    if score >= 90:
        return "A"
    elif score >= 80:
        return "B"
    elif score >= 70:
        return "C"
    else:
        return "F"

: 조건이 2개 이상이면 람다보다 def 함수가 더 명확합니다.

13. 클로저와 함께 사용하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 람다를 반환하는 함수 (클로저 패턴)
def make_multiplier(n):
    """n을 곱하는 함수를 생성"""
    return lambda x: x * n

multiply_by_3 = make_multiplier(3)
multiply_by_10 = make_multiplier(10)

print(multiply_by_3(5))   # 15
print(multiply_by_10(5))  # 50

# 실전 예제: 세금 계산기
def make_tax_calculator(rate):
    """세율에 따른 세금 계산 함수 생성"""
    return lambda amount: amount * rate

income_tax = make_tax_calculator(0.1)   # 소득세 10%
vat = make_tax_calculator(0.1)          # 부가세 10%

print(f"소득세: {income_tax(100000):,}")
print(f"부가세: {vat(100000):,}")

: 클로저 패턴에서 람다는 매우 유용합니다.

14. operator 모듈 활용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from operator import add, mul, itemgetter, attrgetter

numbers = [1, 2, 3, 4, 5]

# ❌ 람다 사용
from functools import reduce
sum_result = reduce(lambda x, y: x + y, numbers)

# ✅ operator 모듈 사용 (더 효율적)
sum_result = reduce(add, numbers)

# 딕셔너리 정렬에 활용
students = [
    {"name": "김철수", "score": 85},
    {"name": "이영희", "score": 92},
]

# ❌ 람다
sorted_students = sorted(students, key=lambda s: s["score"])

# ✅ itemgetter (더 빠르고 명확)
sorted_students = sorted(students, key=itemgetter("score"))

: operator 모듈의 함수들은 람다보다 성능이 좋습니다.

15. docstring 부재 인식

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# ❌ 람다는 docstring을 가질 수 없음
square = lambda x: x ** 2
# square.__doc__는 None

# ✅ def 함수는 docstring으로 문서화 가능
def square(x):
    """
    주어진 수의 제곱을 계산합니다.

    Args:
        x: 제곱할 숫자

    Returns:
        x의 제곱값

    Examples:
        >>> square(5)
        25
    """
    return x ** 2

print(square.__doc__)  # docstring 출력
help(square)           # 도움말 확인 가능

: 함수 문서화가 필요하면 반드시 def를 사용하세요.


📝 오늘 배운 내용 정리

  1. 람다 함수: lambda 매개변수: 표현식 형태의 익명 함수
  2. sorted(): key=lambda 로 정렬 기준 지정
  3. map(): 모든 요소에 함수 적용
  4. filter(): 조건에 맞는 요소만 선택
  5. 사용 시기: 간단한 일회성 함수, 복잡하면 def 사용
  6. PEP 8: 람다를 변수에 할당하지 말고 def 사용
  7. Pythonic: 리스트 컴프리헨션을 먼저 고려

🎯 실습 과제

과제: 상품 관리 시스템

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
products = [
    {"name": "노트북", "price": 1200000, "category": "전자제품", "stock": 5},
    {"name": "마우스", "price": 30000, "category": "전자제품", "stock": 20},
    {"name": "키보드", "price": 80000, "category": "전자제품", "stock": 15},
    {"name": "책상", "price": 150000, "category": "가구", "stock": 8},
    {"name": "의자", "price": 200000, "category": "가구", "stock": 12}
]

# 1. 100,000원 이상 제품만 필터링
expensive = list(filter(lambda p: p["price"] >= 100000, products))
print("고가 제품:")
for p in expensive:
    print(f"- {p['name']}: {p['price']:,}")

# 2. 가격순 정렬 (내림차순)
sorted_by_price = sorted(products, key=lambda p: p["price"], reverse=True)
print("\n가격순 정렬:")
for p in sorted_by_price:
    print(f"- {p['name']}: {p['price']:,}")

# 3. 카테고리별 그룹화 (딕셔너리로)
categories = {}
for product in products:
    category = product["category"]
    if category not in categories:
        categories[category] = []
    categories[category].append(product["name"])

print("\n카테고리별 상품:")
for category, items in categories.items():
    print(f"{category}: {', '.join(items)}")

# 4. 재고가 10개 이상인 제품의 이름만 추출
high_stock_names = list(map(
    lambda p: p["name"],
    filter(lambda p: p["stock"] >= 10, products)
))
print(f"\n재고 충분 상품: {', '.join(high_stock_names)}")

# 5. 각 제품의 총 가치 (가격 * 재고) 계산
total_values = list(map(
    lambda p: {"name": p["name"], "total_value": p["price"] * p["stock"]},
    products
))
print("\n제품별 총 가치:")
for item in sorted(total_values, key=lambda x: x["total_value"], reverse=True):
    print(f"- {item['name']}: {item['total_value']:,}")

🔗 관련 자료


📚 이전 학습

Day 27: *args와 **kwargs

어제는 *args와 **kwargs로 가변 인자를 다루는 방법을 배웠습니다!


📚 다음 학습

Day 29: 스코프와 전역 변수 ⭐⭐⭐

내일은 변수의 유효 범위와 전역/지역 변수를 배웁니다!


“늦었다고 생각할 때가 가장 빠른 시기입니다!” 🚀

Day 28/100 Phase 3: 제어문과 함수 #100DaysOfPython
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.