고급 데코레이터
데코레이터에 인자 전달
파라미터를 받아서 로직을 추상화하면 훨씬 더 강력하고 재사용성이 높아진다.
파라미터를 가진 데코레이터를 만드는 방법
- 중첩 함수(Nested Function) 사용 → 간접 참조(indirection)로 한 단계 더 깊이 만들기
- 데코레이터를 위한 클래스 사용 → 호출 가능한 객체로 만들기 (중첩함수 방법보다 가독성 더 좋음)
- 데코레이터의 본질은 “함수를 받아서 함수를 반환하는 함수” (=고차 함수, 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_limit과allowed_exceptions를 받고, 2단계retry가 원본operation을 받으며, 3단계wrapped가 실제 예외 처리 및 재시도 루프를 수행한다. - 클라이언트는
@with_retry(),@with_retry(retries_limit=5),@with_retry(allowed_exceptions=(AttributeError,))처럼 괄호 안에 원하는 값을 넘겨 재시도 횟수나 예외 유형을 유연하게 제어할 수 있다.
중첩 함수 방식의 한계점
- 이 방식은 대부분의 경우에 잘 작동하지만, 새로운 함수가 추가될 때마다 들여쓰기가 추가되어 너무 많은 중첩 함수가 필요할 수 있음.
- 또 함수는 상태를 저장하지 않기 때문에 객체가 하는 것처럼 내부 데이터를 관리하기가 어렵다는 단점이 존재.
파라미터를 받는 데코레이터를 만들려면 “파라미터를 받는 함수 → 데코레이터 함수 → 래퍼 함수” 이렇게 최소 3단계 중첩이 필요. 간단할 때는 중첩 함수로 충분하지만, 복잡해지면 클래스 방식이 더 읽기 좋고 유지보수하기 좋다.
댓글