파이썬을 사용하다 보면 코드를 재사용하고 기능을 확장하는 데 유용한 데코레이터를 자주 만나게 됩니다. 데코레이터는 기존의 함수를 수정하지 않으면서 기능을 추가할 수 있는 매우 강력한 기능으로, 특히 로그 기록, 캐싱, 인증 등의 작업에 유용합니다.

데코레이터는 기본적으로 함수클래스를 인자로 받아 새로운 함수를 반환하는 함수입니다. 이 방식으로 특정 기능을 함수에 추가하거나 수정 없이 재사용할 수 있습니다. 데코레이터는 파이썬의 고유 기능 중 하나이며, 코드를 효율적으로 관리하고 모듈화하는 데 매우 유리합니다.

이번 글에서는 파이썬 데코레이터의 기초부터 시작해, 실제 개발에서 활용할 수 있는 다양한 데코레이터 예제를 함께 알아보겠습니다.


1. 기본 데코레이터 예제: 접근 전후 알림 추가하기

함수를 호출하기 전후로 알림을 주는 기본적인 데코레이터입니다. API 호출 전후, 파일 작업 전후 등의 상황에서 쉽게 사용할 수 있습니다.

def notify_execution(func):
    def wrapper(*args, **kwargs):
        print(f"[INFO] Starting '{func.__name__}' execution.")
        result = func(*args, **kwargs)
        print(f"[INFO] Finished '{func.__name__}' execution.")
        return result
    return wrapper

@notify_execution
def process_data(data):
    # 데이터 처리 로직
    print("Processing data...")
    return f"Processed {data}"

# 실제 실행
print(process_data("Sample data"))

설명: notify_execution 데코레이터는 함수의 시작과 끝에 알림을 출력합니다. 주로 데이터 처리나 작업 흐름을 추적하는 데 유용합니다.


2. 입력 데이터 유효성 검사 데코레이터

함수에 전달되는 인자의 유효성을 검사하는 데코레이터입니다. 웹 애플리케이션이나 데이터 처리 파이프라인에서 데이터 검증을 간단히 추가할 수 있습니다.

def validate_positive_args(func):
    def wrapper(*args):
        # 모든 인자가 양수인지 확인
        if not all(isinstance(arg, (int, float)) and arg > 0 for arg in args):
            raise ValueError("All arguments must be positive numbers")
        return func(*args)
    return wrapper

@validate_positive_args
def calculate_volume(length, width, height):
    return length * width * height

# 실제 실행
print(calculate_volume(5, 10, 3))  # 정상 실행
# print(calculate_volume(5, -10, 3))  # ValueError 발생

설명: validate_positive_args는 모든 인자가 양수인지 확인하고, 그렇지 않을 경우 예외를 발생시킵니다. 유효성 검증을 위해 여러 함수에 일관된 검사를 적용할 수 있습니다.


3. 재시도 (Retry) 데코레이터

API 요청과 같은 외부 자원을 사용할 때 일시적인 네트워크 문제나 서버 응답 실패가 발생할 수 있습니다. 이를 대비해 재시도 데코레이터를 사용해보겠습니다.

import time

def retry(retries=3, delay=1):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for attempt in range(1, retries + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"Attempt {attempt} failed: {e}")
                    time.sleep(delay)
            raise Exception("All attempts failed")
        return wrapper
    return decorator

@retry(retries=3, delay=2)
def fetch_data():
    # 외부 API 호출을 시뮬레이션
    if time.time() % 2 < 1:
        raise ConnectionError("Failed to fetch data")
    return "Data fetched successfully"

# 실제 실행
print(fetch_data())

설명: retry 데코레이터는 실패할 가능성이 있는 함수 호출을 특정 횟수만큼 재시도합니다. 예기치 않은 일시적 오류에 대비해 유용하게 사용할 수 있습니다.


4. 캐시 데코레이터 (메모이제이션)

복잡한 계산 결과를 저장해두고, 동일한 인자로 호출될 때 캐싱된 결과를 반환하는 데코레이터입니다. CPU 사용량이 높은 작업에서 성능을 개선할 수 있습니다.

def cache(func):
    cached_results = {}
    def wrapper(*args):
        if args in cached_results:
            print(f"[CACHE] Returning cached result for {args}")
            return cached_results[args]

        result = func(*args)
        cached_results[args] = result
        print(f"[CACHE] Caching result for {args}")
        return result
    return wrapper

@cache
def expensive_calculation(x, y):
    # 복잡한 계산 시뮬레이션
    time.sleep(1)
    return x * y

# 실제 실행
print(expensive_calculation(5, 3))  # 캐싱하지 않은 결과 계산
print(expensive_calculation(5, 3))  # 캐싱된 결과 반환

설명: cache 데코레이터는 동일한 인자로 반복 호출되는 함수에서 캐시된 결과를 반환합니다. 계산 속도를 높이고 서버 부하를 줄이는 데 유용합니다.


5. 권한 기반 접근 제어 데코레이터

웹 애플리케이션에서 특정 역할이 있는 사용자만 특정 기능에 접근할 수 있도록 제한할 수 있습니다.

def role_required(role):
    def decorator(func):
        def wrapper(user, *args, **kwargs):
            if user.get("role") != role:
                raise PermissionError(f"Access denied: User needs '{role}' role")
            return func(user, *args, **kwargs)
        return wrapper
    return decorator

@role_required("admin")
def delete_content(user, content_id):
    print(f"{user['name']} deleted content {content_id}")

# 사용자 데이터
admin_user = {"name": "Alice", "role": "admin"}
regular_user = {"name": "Bob", "role": "user"}

# 실제 실행
delete_content(admin_user, 42)  # 정상 접근
# delete_content(regular_user, 42)  # PermissionError 발생

설명: role_required 데코레이터는 사용자가 특정 권한이 있을 때만 함수를 호출할 수 있도록 제한합니다. 보안이 중요한 웹 애플리케이션에 유용합니다.


6. 함수 실행 전후 시간 측정 (성능 프로파일링)

복잡한 계산이나 데이터 처리 함수의 성능을 측정하여, 실행 시간이 오래 걸리는지 확인할 수 있습니다.

import time

def profile_execution(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"[PROFILE] Execution time for '{func.__name__}': {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@profile_execution
def complex_task():
    # 연산 시간이 오래 걸리는 작업을 시뮬레이션
    time.sleep(2)
    return "Task complete"

# 실제 실행
complex_task()

설명: profile_execution 데코레이터는 함수의 실행 시간을 계산해줍니다. 성능 최적화가 필요한 함수에 적용해 느린 작업을 추적할 수 있습니다.


7. 함수의 성공 및 실패 결과 로깅

함수가 성공했는지, 오류가 발생했는지를 로깅하는 데코레이터입니다. 로그를 통해 함수를 모니터링하고 디버깅할 때 유용합니다.

import logging

logging.basicConfig(level=logging.INFO)

def log_result(func):
    def wrapper(*args, **kwargs):
        try:
            result = func(*args, **kwargs)
            logging.info(f"Function '{func.__name__}' executed successfully with result: {result}")
            return result
        except Exception as e:
            logging.error(f"Function '{func.__name__}' failed with error: {e}")
            raise e
    return wrapper

@log_result
def divide(a, b):
    return a / b

# 실제 실행
print(divide(10, 2))  # 정상 실행
# print(divide(10, 0))  # ZeroDivisionError 발생 및 로깅

설명: log_result 데코레이터는 함수가 정상적으로 실행되었는지, 오류가 발생했는지 로깅합니다. 로그 기록을 통해 디버깅하고, 오류를 추적할 때 유용합니다.


이렇게 다양한 기능의 데코레이터를 통해 함수의 실행 전후 알림, 인자 검증, 캐싱, 권한 관리, 성능 프로파일링, 실행 결과 로깅 등의 기능을 손쉽게 추가할 수 있습니다. 각각의 데코레이터는 현실적인 상황에서 코드의 효율성과 보안성을 높이며, 디버깅과 유지보수에도 큰 도움을 줍니다.


+ Recent posts