기본값을 가진 데코레이터
- 데코레이터에 기본값을 주는 이유는 사용자가 파라미터를 깜빡 잊고 안 넘겨도 동작하게 하기 위해서이다.
# 기본값 사용
@WithRetry()
@WithRetry(retries_limit=5)
# 괄호없이 사용하면 문제 발생
@WithRetry
def my_function():
...
- 문제가 발생하는 이유
@WithRetry()→WithRetry()를 먼저 호출해서 인스턴스를 만들고, 그 인스턴스가 함수를 받음@WithRetry→WithRetry클래스 자체가 함수를 바로 받게 됨 (즉__init__이 아니라 클래스가 직접 호출되는 상황이 되어 버림)
- 괄호가 없는 경우는 첫 번째 파라미터로 함수가 전달되지만, 괄호가 있는 경우는 첫 번째 파라미터로
None이 전달된다. - 문서상으로는 괄호를 사용하는 방식
@retry())이 올바른 방식
두 가지 호출 방식(괄호 있음/없음)을 모두 지원하는 방법
1. 가장 간단한 방법: 첫 번째 파라미터가 None인지 확인
def decorator(function=None, *, x=DEFAULT_X, y=DEFAULT_Y):
if function is None:
# @decorator(x=3, y=4) 형태로 호출된 경우
def decorated(function):
@wraps(function)
def wrapped():
return function(x, y)
return wrapped
return decorated
else:
# @decorator 형태로 괄호 없이 호출된 경우
@wraps(function)
def wrapped():
return function(x, y)
return wrapped
2. 더 깔끔한 방법 (lambda 또는 partial 사용):
from functools import partial
def decorator(function=None, *, x=DEFAULT_X, y=DEFAULT_Y):
if function is None:
return lambda f: decorator(f, x=x, y=y)
# 또는
# return partial(decorator, x=x, y=y)
@wraps(function)
def wrapped():
return function(x, y)
return wrapped
- 함수가 안 넘어오면 → “나중에 함수를 받겠다”는 의미의 함수를 반환
- 함수가 넘어오면 → 바로 래핑해서 반환
def with_retry(*, retries_limit=3, allowed_exceptions=None): # * 뒤에 오는 것은 키워드 전용
...
- 위치 인자로 넘기면 의미가 불명확해짐
- 키워드로 강제하면 코드의 의도가 명확해짐
파라미터가 아예 없는 데코레이터라면 다음과 같이 명시할 수도 있음
def retry(operation, /): # / 앞은 위치 전용
...
- 하지만 필수는 아니고, 호출 방식을 명시적으로 표현하고 싶을 때 사용하는 방법
댓글