파이썬 데코레이터는 함수(또는 클래스)를 받아 다른 호출 가능한 객체를 돌려주는 함수다. @deco를 함수 정의 위에 쓰면 func = deco(func)와 같다. 원래 함수 앞뒤에 로깅·권한 검사·캐시 같은 기능을 덧붙일 때 쓴다.
import functools, logging, time
def timed(func):
@functools.wraps(func) # 원래 이름·docstring 보존
def wrapper(*args, **kwargs):
start = time.perf_counter()
try:
return func(*args, **kwargs)
finally:
logging.info("%s %.1fms", func.__name__, (time.perf_counter() - start) * 1000)
return wrapper
@timed
def fetch(user_id): ...- 함수가 일급 객체(first-class object)라서 가능하다. 함수를 인자로 받고, 안에서 함수를 만들어 돌려준다(클로저)
functools.wraps를 빼면 감싼 함수의 이름이wrapper가 되어 디버깅·문서화가 헷갈린다*args, **kwargs로 받아 그대로 넘기면 어떤 시그니처의 함수에도 쓸 수 있다(*args·**kwargs와 언패킹)
인자를 받는 데코레이터
def retry(times):
def deco(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for i in range(times):
try:
return func(*args, **kwargs)
except ConnectionError:
if i == times - 1:
raise
return wrapper
return deco
@retry(times=3)
def call_api(): ...클래스로 만들기
__call__을 구현한 클래스의 인스턴스도 호출할 수 있으므로 데코레이터가 될 수 있다. 상태(호출 횟수 등)를 들고 있어야 할 때 쓴다. 하지만 보통은 함수로 충분하다.
자주 보는 예
- 표준 라이브러리:
@property,@classmethod,@staticmethod,@functools.cache,@dataclass - 장고:
@login_required,@transaction.atomic - 디버깅: 인자와 반환값을 찍는 데코레이터를 잠깐 붙였다 뗀다. 함수 안에서는
vars()로 지역 변수를 한 번에 보고,breakpoint()로 디버거를 연다
감싸서 기능을 더하는 구조는 데코레이터 패턴과 같고, 스프링에서는 AOP가 프록시로 같은 일을 한다. 타입스크립트의 데코레이터는 문법은 비슷해도 동작 시점이 다르다(TypeScript 데코레이터).