Skip to main content

람다와 클로저

학습 목표

  • lambda 표현식으로 간단한 익명 함수를 작성할 수 있다
  • map(), filter(), reduce() 고차 함수를 활용할 수 있다
  • 클로저(Closure)의 개념과 활용 패턴을 이해한다

왜 중요한가

람다와 고차 함수는 함수형 프로그래밍의 핵심 도구입니다. 짧은 콜백 함수, 정렬 키, 데이터 변환 파이프라인에서 코드를 간결하게 만듭니다. 클로저는 데코레이터와 팩토리 패턴의 기반이 되므로, 이후 학습의 토대가 됩니다.

lambda 표현식

lambda는 한 줄짜리 익명 함수(Anonymous Function)입니다.
# 일반 함수
def square(x):
    return x ** 2

# lambda 동치
square = lambda x: x ** 2

print(square(5))  # 25

# lambda 문법: lambda 매개변수: 표현식
add = lambda a, b: a + b
greet = lambda name: f"안녕, {name}!"

print(add(3, 4))        # 7
print(greet("철수"))     # "안녕, 철수!"

주요 활용

# 정렬 키
students = [("김철수", 85), ("이영희", 92), ("박민수", 78)]
students.sort(key=lambda s: s[1], reverse=True)
print(students)  # [("이영희", 92), ("김철수", 85), ("박민수", 78)]

# 딕셔너리 정렬
scores = {"김철수": 85, "이영희": 92, "박민수": 78}
sorted_items = sorted(scores.items(), key=lambda x: x[1])

# 조건부 표현
classify = lambda x: "양수" if x > 0 else "음수" if x < 0 else "영"
print(classify(5))    # "양수"
print(classify(-3))   # "음수"
lambda는 간단한 표현식에만 사용하세요. 복잡한 로직은 def로 정의하는 것이 가독성에 좋습니다.
# 나쁜 예 - 너무 복잡한 lambda
process = lambda x: x.strip().lower().replace(" ", "_") if x else ""

# 좋은 예 - def 사용
def process(x):
    """텍스트를 정규화합니다."""
    if not x:
        return ""
    return x.strip().lower().replace(" ", "_")

고차 함수 (Higher-Order Functions)

고차 함수는 함수를 인자로 받거나 함수를 반환하는 함수입니다.

map()

# 모든 요소에 함수 적용
numbers = [1, 2, 3, 4, 5]
squared = list(map(lambda x: x ** 2, numbers))
print(squared)  # [1, 4, 9, 16, 25]

# 여러 이터러블
a = [1, 2, 3]
b = [10, 20, 30]
sums = list(map(lambda x, y: x + y, a, b))
print(sums)  # [11, 22, 33]

# 타입 변환
str_numbers = ["1", "2", "3"]
int_numbers = list(map(int, str_numbers))
print(int_numbers)  # [1, 2, 3]

filter()

# 조건을 만족하는 요소만 추출
numbers = [1, -2, 3, -4, 5, -6]
positives = list(filter(lambda x: x > 0, numbers))
print(positives)  # [1, 3, 5]

# None 전달 - Falsy 값 제거
data = [0, 1, "", "hello", None, True, False, [], [1]]
truthy = list(filter(None, data))
print(truthy)  # [1, "hello", True, [1]]

reduce()

from functools import reduce

# 누적 연산
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda acc, x: acc + x, numbers)
print(total)  # 15

# 최댓값 찾기
maximum = reduce(lambda a, b: a if a > b else b, numbers)
print(maximum)  # 5

# 초기값 지정
product = reduce(lambda acc, x: acc * x, numbers, 1)
print(product)  # 120
map()/filter() 대신 리스트 컴프리헨션을 사용하는 것이 더 Pythonic합니다.
# map + filter
result = list(map(lambda x: x**2, filter(lambda x: x > 0, numbers)))

# 리스트 컴프리헨션 (더 읽기 쉬움)
result = [x**2 for x in numbers if x > 0]

클로저 (Closure)

클로저는 외부 함수의 변수를 기억하는 내부 함수입니다.
def make_multiplier(n):
    """n배 곱하는 함수를 생성합니다."""
    def multiplier(x):
        return x * n    # n은 외부 함수의 변수 (자유 변수)
    return multiplier

double = make_multiplier(2)
triple = make_multiplier(3)

print(double(5))   # 10
print(triple(5))   # 15

# 클로저가 기억하는 변수 확인
print(double.__closure__[0].cell_contents)  # 2

클로저 활용 패턴

# 로거 팩토리
def make_logger(prefix):
    def log(message):
        print(f"[{prefix}] {message}")
    return log

info = make_logger("INFO")
error = make_logger("ERROR")

info("서버 시작")      # [INFO] 서버 시작
error("연결 실패")     # [ERROR] 연결 실패

# 카운터 (상태 유지)
def make_counter(start=0):
    count = [start]  # 리스트로 감싸서 수정 가능하게

    def counter():
        count[0] += 1
        return count[0]
    return counter

counter = make_counter()
print(counter())   # 1
print(counter())   # 2

AI/ML에서의 활용

# 손실 함수 팩토리 (클로저 활용)
def make_weighted_loss(class_weights):
    """클래스 가중치를 적용한 손실 함수를 생성합니다."""
    def weighted_loss(predictions, targets):
        losses = []
        for pred, target in zip(predictions, targets):
            weight = class_weights.get(target, 1.0)
            loss = -weight * (target * pred + (1 - target) * (1 - pred))
            losses.append(loss)
        return sum(losses) / len(losses)
    return weighted_loss

# 불균형 데이터셋용 가중치
loss_fn = make_weighted_loss({0: 1.0, 1: 5.0})

# 데이터 전처리 파이프라인
pipeline = [
    lambda x: x.strip(),
    lambda x: x.lower(),
    lambda x: x.replace("\n", " "),
]

def apply_pipeline(text, steps):
    for step in steps:
        text = step(text)
    return text

result = apply_pipeline("  Hello\nWorld  ", pipeline)
print(result)  # "hello world"
아니요. lambda는 단일 표현식만 허용합니다. 여러 줄이 필요하면 def를 사용하세요.
둘 다 상태를 유지할 수 있습니다. 클로저는 간단한 상태 유지에 적합하고, 클래스는 여러 메서드와 복잡한 상태 관리에 적합합니다. 상태가 2-3개 이하이면 클로저, 그 이상이면 클래스를 고려하세요.

체크리스트

  • lambda 표현식을 정렬 키, 콜백 등에 활용할 수 있다
  • map(), filter()와 리스트 컴프리헨션을 비교할 수 있다
  • 클로저의 동작 원리와 자유 변수를 설명할 수 있다
  • 클로저로 팩토리 패턴을 구현할 수 있다

다음 문서