파이썬에서의 클로저와 데코레이터: 심화 개념과 실용적 활용

함수 객체의 다양한 활용 방식

파이썬에서 함수는 일급 객체(first-class object)로서 변수에 할당하거나, 컨테이너에 저장하며, 인자로 전달하거나 반환값으로 사용할 수 있습니다.

함수를 변수에 할당

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

operation = add
operation(15, 25)  # 출력: 40

함수를 컨테이너에 저장

def multiply(x, y):
    return x * y

operations = {'multiply': multiply}
result = operations['multiply'](7, 8)
print(result)  # 출력: 56

함수를 다른 함수의 인자로 전달

def subtract(x, y):
    return x - y

def execute(a, b, operation_func):
    return operation_func(a, b) + a

outcome = execute(10, 3, subtract)
print(outcome)  # 출력: 17

함수를 반환값으로 사용

def get_calculator():
    def divide(x, y):
        return x / y if y != 0 else None
    return divide

calc = get_calculator()
print(calc(20, 4))  # 출력: 5.0

클로저(Closure)

클로저란 외부 함수의 지역 변수를 내부 함수가 참조하고 있는 상태를 말합니다. 이 구조는 데이터 캡슐화와 상태 유지에 유용합니다.

기본적인 클로저 예제

def outer_scope():
    counter = 0
    def inner_logic():
        nonlocal counter
        counter += 1
        print(f"호출 횟수: {counter}")
    return inner_logic

counter_fn = outer_scope()
counter_fn()  # 호출 횟수: 1
counter_fn()  # 호출 횟수: 2

동적 파라미터를 사용한 클로저

def create_greeting(prefix):
    def greet(name):
        print(f"{prefix}, {name}!")
    return greet

hello = create_greeting("안녕하세요")
hi = create_greeting("반갑습니다")

hello("지훈")  # 안녕하세요, 지훈!
hi("민수")     # 반갑습니다, 민수!

데코레이터(Decorator)

데코레이터는 기존 함수의 코드나 호출 방식을 변경하지 않고도 새로운 동작을 추가할 수 있는 강력한 패턴입니다. 주로 로깅, 인증, 성능 측정 등에 사용됩니다.

실행 시간 측정 데코레이터

import time

def timing_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} 실행 시간: {end_time - start_time:.4f}s")
        return result
    return wrapper

@timing_decorator
def slow_task():
    time.sleep(1)
    print("작업 완료")

slow_task()

로그인 인증 데코레이터

user_session = {'logged_in': False, 'username': None}

def require_auth(func):
    def secure_wrapper(*args, **kwargs):
        if not user_session['logged_in']:
            return False, "접근 거부: 로그인이 필요합니다."
        return func(*args, **kwargs)
    return secure_wrapper

@require_auth
def withdraw_money(amount):
    print(f"{amount}원 출금 처리 중...")
    return True, "출금 완료"

# 세션 미인증 상태
print(withdraw_money(50000))  # (False, '접근 거부: 로그인이 필요합니다.')

# 인증 후
user_session['logged_in'] = True
user_session['username'] = 'james'
print(withdraw_money(50000))  # 출금 처리됨

다중 데코레이터 적용

여러 데코레이터를 동시에 사용할 수 있으며, 위에서 아래로 순서대로 감쌉니다.

def log_calls(func):
    def logger(*args, **kwargs):
        print(f"[LOG] 호출됨: {func.__name__}")
        return func(*args, **kwargs)
    return logger

def enforce_admin(func):
    def admin_guard(*args, **kwargs):
        if user_session.get('role') != 'admin':
            return False, "권한 부족: 관리자만 접근 가능"
        return func(*args, **kwargs)
    return admin_guard

@log_calls
@enforce_admin
def delete_user(user_id):
    print(f"사용자 {user_id} 삭제")
    return True, "삭제 성공"

user_session['role'] = 'user'
print(delete_user(123))
# [LOG] 호출됨: delete_user
# (False, '권한 부족: 관리자만 접근 가능')

매개변수가 있는 데코레이터

외부 설정이나 조건에 따라 다르게 동작하는 데코레이터를 만들 수 있습니다.

def retry_on_failure(max_attempts=3):
    def decorator(target_func):
        def handler(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return target_func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        print(f"최대 재시도 초과: {e}")
                        raise
                    print(f"시도 {attempt} 실패, 재시도 중...")
        return handler
    return decorator

@retry_on_failure(max_attempts=2)
def unstable_api_call():
    import random
    if random.choice([True, False]):
        raise ConnectionError("네트워크 오류")
    print("API 호출 성공")
    
# 여러 번 실행해보면 실패 후 재시도되는 것을 확인할 수 있음

데코레이터 메타데이터 보존

데코레이터를 사용하면 원본 함수의 이름, 설명문(docstring), 매개변수 정보 등이 손실될 수 있습니다. 이를 방지하기 위해 functools.wraps를 사용합니다.

from functools import wraps

def enhanced_logger(func):
    @wraps(func)
    def logged_function(*args, **kwargs):
        """모든 함수 호출을 기록하는 래퍼"""
        print(f"실행: {func.__name__}")
        return func(*args, **kwargs)
    return logged_function

@enhanced_logger
def sample_task():
    """샘플 작업을 수행합니다."""
    pass

print(sample_task.__name__)   # sample_task (wraps 없으면 logged_function)
print(sample_task.__doc__)    # 샘플 작업을 수행합니다.

태그: python Closure decorator functools.wraps First-class Function

7월 27일 07:27에 게시됨