데코레이터는 파이썬에서 2.4 버전부터 문법 설탕으로 등장하여 고차 함수를 보다 쉽게 사용할 수 있도록 하였습니다. 고차 함수는 다른 함수를 인수로 받거나 반환하는 함수입니다. 함수의 기능을 확장하는 접근 방식의 진화는 @decorator라는 간결하고 표현력 있는 형식으로 이어졌습니다.
대규모 프로젝트에서는 종종 함수에 추가적인 로직(로깅, 접근 제어, 캐싱, 실행 시간 측정 등)을 수정하거나 래핑해야 합니다. 데코레이터 없이 수동으로 래핑 함수를 호출하면 코드가 불필요하게 늘어납니다.
데코레이터를 사용하면 반복적인 측면을 별도의 래퍼로 분리하여 코드의 가독성과 재사용성을 높일 수 있습니다. @decorator를 사용하여 함수와 메서드에 세련되게 기능을 추가할 수 있습니다:
코드 예:
import time def timing_decorator(func): def wrapper(*args, **kwargs): start = time.time() result = func(*args, **kwargs) print(f'Elapsed: {time.time() - start:.3f}s') return result return wrapper @timing_decorator def slow_function(): time.sleep(0.5) slow_function() # 실행 시간 출력
주요 특징:
데코레이터가 래핑된 함수의 시그니처를 변경할 수 있나요?
종종 데코레이터는 함수의 시그니처를 변경하지 않는다고 잘못 생각합니다. 실제로 functools.wraps 모듈을 사용하지 않으면 메타 정보가 소실되어 자동 문서화 또는 introspection에서 예상치 못한 오류가 발생할 수 있습니다.
코드 예:
from functools import wraps def decorator(func): @wraps(func) def wrapper(*args): return func(*args) return wrapper
데코레이터에 매개변수를 전달할 수 있나요?
종종 불가능하다고 대답합니다. 하지만 실제로는 데코레이터를 반환하는 추가 수준의 중첩 함수를 통해 매개변수를 받는 데코레이터를 만들 수 있습니다.
코드 예:
def repeat(n): def decorator(func): def wrapper(*args, **kwargs): result = None for _ in range(n): result = func(*args, **kwargs) return result return wrapper return decorator @repeat(3) def hello(): print("Hello!")
하나의 함수에 여러 개의 데코레이터를 적용할 수 있나요? 실행 순서는 어떻게 되나요?
종종 순서가 중요하지 않거나 코드에서 데코레이터의 배치 순서와 같다고 잘못 생각합니다. 실제로는 가장 아래의 데코레이터가 먼저 적용되고, 그 다음으로 위쪽으로 적용됩니다.
코드 예:
def dec1(f): def wrapper(*a, **k): print("dec1") return f(*a, **k) return wrapper def dec2(f): def wrapper(*a, **k): print("dec2") return f(*a, **k) return wrapper @dec1 @dec2 def f(): print("core") f() # dec1, dec2, core
10개의 함수에 수동으로 시간 로깅 로직이 추가됨, 코드 복사로 작성됨.
장점:
단점:
모든 타이밍 로직이 데코레이터로 분리되어, 필요한 모든 함수가 @timing_decorator로 감싸짐.
장점:
단점:
functools.wraps 없이 시그니처 정보가 손실될 수 있음, 주의가 필요함;