본문 바로가기
👩🏻‍💻Computer Science/clean-code

Chapter5. 고급 데코레이터(데코레이터에 인자 전달)

by devhertz 2026. 9. 6.

고급 데코레이터

데코레이터에 인자 전달

파라미터를 받아서 로직을 추상화하면 훨씬 더 강력하고 재사용성이 높아진다.

 

파라미터를 가진 데코레이터를 만드는 방법

  1. 중첩 함수(Nested Function) 사용 → 간접 참조(indirection)로 한 단계 더 깊이 만들기
  2. 데코레이터를 위한 클래스 사용 → 호출 가능한 객체로 만들기 (중첩함수 방법보다 가독성 더 좋음)
  • 데코레이터의 본질은 “함수를 받아서 함수를 반환하는 함수” (=고차 함수, Higher-order function)
    • 파라미터를 추가하려면 다른 수준의 간접 참조가 필요함
    • 즉, 최소 3단계 중첩 함수가 필요.
단계 역할 설명
1단계 파라미터를 받는 함수 @decorator(값)에서 값을 받음
2단계 실제 데코레이터 원본 함수를 받음
3단계 래퍼(wrapper) 실제 실행 로직을 담당하고 결과를 반환

이전 예제 활용) 재시도 횟수와 예외를 파라미터로 받는 데코레이터

 

기존 데코레이터 안에 고정되어 있던 재시도 횟수를 인스턴스마다 다르게 지정하고 기본값을 추가하기 위해 파라미터용 외부 함수(with_retry)를 한 단계 더 추가.

_DEFAULT_RETRIES_LIMIT = 3

def with_retry(                               # 1단계: 파라미터를 받는 함수
    retries_limit: int = _DEFAULT_RETRIES_LIMIT,
    allowed_exceptions: Optional[Sequence[Exception]] = None,
):
    allowed_exceptions = allowed_exceptions or (ControlledException,)

    def retry(operation):                     # 2단계: 실제 데코레이터
        @wraps(operation)
        def wrapped(*args, **kwargs):         # 3단계: 래퍼
            last_raised = None
            for _ in range(retries_limit):
                try:
                    return operation(*args, **kwargs)
                except allowed_exceptions as e:
                    logger.warning(
                        "%s 재시도, 원인: %s",
                        operation.__qualname__, e
                    )
                    last_raised = e
            raise last_raised
        return wrapped
    return retry                              # 1단계에서 2단계 함수를 반환
# 사용 예
# decorator_parametrized_1.py

@with_retry()                                          # 기본값 사용 (3회, ControlledException)
def run_operation(task):
    return task.run()

@with_retry(retries_limit=5)                           # 재시도 5회로 변경
def run_with_custom_retries_limit(task):
    return task.run()

@with_retry(allowed_exceptions=(AttributeError,))      # 허용 예외 변경
def run_with_custom_exceptions(task):
    return task.run()

@with_retry(
    retries_limit=4,
    allowed_exceptions=(ZeroDivisionError, AttributeError)
)
def run_with_custom_parameters(task):
    return task.run()

 

동작

  • 1단계인 with_retry에서 retries_limitallowed_exceptions를 받고, 2단계 retry가 원본 operation을 받으며, 3단계 wrapped가 실제 예외 처리 및 재시도 루프를 수행한다.
  • 클라이언트는 @with_retry(), @with_retry(retries_limit=5), @with_retry(allowed_exceptions=(AttributeError,)) 처럼 괄호 안에 원하는 값을 넘겨 재시도 횟수나 예외 유형을 유연하게 제어할 수 있다.

중첩 함수 방식의 한계점

  • 이 방식은 대부분의 경우에 잘 작동하지만, 새로운 함수가 추가될 때마다 들여쓰기가 추가되어 너무 많은 중첩 함수가 필요할 수 있음.
  • 또 함수는 상태를 저장하지 않기 때문에 객체가 하는 것처럼 내부 데이터를 관리하기가 어렵다는 단점이 존재.
정리

파라미터를 받는 데코레이터를 만들려면 “파라미터를 받는 함수 → 데코레이터 함수 → 래퍼 함수” 이렇게 최소 3단계 중첩이 필요. 간단할 때는 중첩 함수로 충분하지만, 복잡해지면 클래스 방식이 더 읽기 좋고 유지보수하기 좋다.

댓글