Python 데코레이터 패턴 모음

재시도(retry), 캐싱(cache), 실행 시간 측정(timer), 권한 확인(auth) 등 실무에서 자주 쓰이는 Python 데코레이터 패턴들입니다.

Gist
import time
import functools
import logging
from typing import Callable, Any, Dict

logger = logging.getLogger(__name__)

def timer(func: Callable) -> Callable:
    """함수의 실행 시간을 측정합니다."""
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        duration = end_time - start_time
        print(f"[{func.__name__}] executed in {duration:.4f}s")
        return result
    return wrapper

def retry(retries: int = 3, delay: float = 1.0) -> Callable:
    """실패 시 지정된 횟수만큼 재시도합니다."""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            last_exception = None
            for i in range(retries):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    last_exception = e
                    logger.warning(f"Retry {i+1}/{retries} for {func.__name__} after error: {e}")
                    time.sleep(delay)
            raise last_exception
        return wrapper
    return decorator

def cache(func: Callable) -> Callable:
    """함수의 결과값을 메모리에 캐싱합니다."""
    memo: Dict[tuple, Any] = {}
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        key = (args, tuple(sorted(kwargs.items())))
        if key not in memo:
            memo[key] = func(*args, **kwargs)
        return memo[key]
    return wrapper

def require_auth(role: str = "user") -> Callable:
    """가상의 권한 확인을 수행합니다."""
    def decorator(func: Callable) -> Callable:
        @functools.wraps(func)
        def wrapper(user_context: dict, *args, **kwargs):
            if user_context.get("role") != role:
                raise PermissionError(f"Required role: {role}, got: {user_context.get('role')}")
            return func(user_context, *args, **kwargs)
        return wrapper
    return decorator

# 사용 예시
@timer
@retry(retries=2)
@cache
def expensive_operation(n: int) -> int:
    time.sleep(0.5)
    return n * 2

@require_auth(role="admin")
def delete_item(user: dict, item_id: int):
    print(f"Item {item_id} deleted by {user['name']}")

if __name__ == "__main__":
    print(expensive_operation(10))
    print(expensive_operation(10)) # 캐시 히트
    
    admin_user = {"name": "Alice", "role": "admin"}
    delete_item(admin_user, 123)