Decorator
데코레이터
함수/클래스 동작을 수정하는 래퍼. Python @decorator 문법. 메타프로그래밍.
데코레이터
함수/클래스 동작을 수정하는 래퍼. Python @decorator 문법. 메타프로그래밍.
데코레이터는 기존 함수나 클래스의 코드를 직접 수정하지 않고 새로운 기능을 추가하는 디자인 패턴입니다. 원본 코드를 감싸는(wrapping) 방식으로 동작하며, 로깅, 인증, 캐싱 등의 횡단 관심사(cross-cutting concerns)를 깔끔하게 분리할 수 있습니다.
Python에서 데코레이터는 @ 기호를 사용하는 문법적 설탕(syntactic sugar)으로, 함수를 인자로 받아 새로운 함수를 반환하는 고차 함수입니다. @decorator는 사실 func = decorator(func)와 동일한 의미이며, 2003년 PEP 318에서 도입되어 현재 Python의 핵심 기능이 되었습니다.
TypeScript에서 데코레이터는 클래스, 메서드, 프로퍼티, 파라미터에 메타데이터를 추가하거나 동작을 수정하는 특별한 선언입니다. 현재 TC39 Stage 3 제안 단계이며, Angular, NestJS 등 주요 프레임워크에서 의존성 주입과 라우팅에 필수적으로 사용됩니다.
실무에서 데코레이터는 AOP(관점 지향 프로그래밍)의 핵심 도구로, 비즈니스 로직과 부가 기능을 분리하여 코드 재사용성과 유지보수성을 크게 향상시킵니다. Flask의 @app.route, Django의 @login_required, NestJS의 @Controller 등이 대표적인 예시입니다.
# 1. 기본 데코레이터 - 실행 시간 측정
import time
import functools
def timer(func):
@functools.wraps(func) # 원본 함수 메타데이터 보존
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
end = time.perf_counter()
print(f"{func.__name__} 실행 시간: {end - start:.4f}초")
return result
return wrapper
@timer
def slow_function():
time.sleep(1)
return "완료"
slow_function() # 출력: slow_function 실행 시간: 1.0012초
# 2. 인자를 받는 데코레이터 - 재시도 로직
def retry(max_attempts=3, delay=1):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts:
raise
print(f"시도 {attempt} 실패, {delay}초 후 재시도...")
time.sleep(delay)
return wrapper
return decorator
@retry(max_attempts=3, delay=2)
def fetch_data(url):
# API 호출 로직
pass
# 3. 클래스 데코레이터 - 싱글톤 패턴
def singleton(cls):
instances = {}
@functools.wraps(cls)
def get_instance(*args, **kwargs):
if cls not in instances:
instances[cls] = cls(*args, **kwargs)
return instances[cls]
return get_instance
@singleton
class Database:
def __init__(self):
print("DB 연결 생성")
db1 = Database() # 출력: DB 연결 생성
db2 = Database() # 출력 없음 (같은 인스턴스 반환)
print(db1 is db2) # True
"API 엔드포인트마다 인증 로직이 중복되고 있어요. @login_required 데코레이터를 만들어서 공통으로 적용하면 코드 중복을 줄이고 보안 정책 변경 시 한 곳만 수정하면 됩니다."
"데코레이터 패턴은 OCP(개방-폐쇄 원칙)을 잘 구현하는 방법입니다. 기존 함수를 수정하지 않고 확장할 수 있고, Python에서는 functools.wraps를 사용해 원본 함수의 __name__, __doc__ 같은 메타데이터를 보존해야 디버깅이 쉬워집니다."
"이 데코레이터에서 functools.wraps가 빠져있네요. 이렇게 되면 decorated 함수의 __name__이 'wrapper'로 나와서 로깅이나 디버깅할 때 헷갈릴 수 있어요. @functools.wraps(func) 추가해주세요."
@functools.wraps(func)를 빠뜨리면 원본 함수의 __name__, __doc__, __annotations__이 손실됩니다. 디버깅 시 함수 이름이 모두 'wrapper'로 나타나 추적이 어려워집니다.
여러 데코레이터가 쌓이면 아래에서 위로 적용됩니다. @a @b @c def func()는 a(b(c(func)))와 같습니다. 순서에 따라 동작이 달라질 수 있으니 의도한 순서인지 확인하세요.
@decorator(arg)처럼 인자를 받으려면 함수를 한 단계 더 감싸야 합니다. decorator(arg) -> actual_decorator(func) -> wrapper(*args) 구조로 3중 중첩이 필요합니다.