데코레이터의 핵심 개념
데코레이터는 기존 함수의 동작을 변경하지 않고도 새로운 기능을 주입할 수 있는 고급 문법입니다. 함수를 인자로 받아 기능을 확장한 뒤, 확장된 함수를 반환하는 구조로 동작합니다.
파이썬에서 @ 기호를 통해 데코레이터를 적용하면, 함수 정의 시점에 자동으로 래핑이 이루어집니다.
def 확장_래퍼(원본_함수):
"""함수 실행 전후에 로그를 출력하는 데코레이터"""
print("래퍼 초기화 단계")
def 감싸는_함수(*위치인자, **키워드인자):
print("[시작] 원본 함수 호출 전")
결과 = 원본_함수(*위치인자, **키워드인자)
print("[종료] 원본 함수 호출 후")
return 결과
print("래퍼 초기화 완료")
return 감싸는_함수
적용 방식:
@확장_래퍼
def 대상_작업():
print("핵심 로직 실행")
# 실행 시: 대상_작업() → 감싸는_함수() → 원본_대상_작업()
메타데이터 보존 기법
데코레이터 적용 후 함수의 __name__, __doc__ 등 메타데이터가 래퍼 함수의 것으로 대체는 문제가 있습니다. 이를 해결하는 두 가지 방법이 있습니다.
수동 복사 방식:
def 확장_래퍼(원본_함수):
def 감싸는_함수(*위치인자, **키워드인자):
return 원본_함수(*위치인자, **키워드인자)
# 메타데이터 수동 이전
감는_함수.__name__ = 원본_함수.__name__
감싸는_함수.__doc__ = 원본_함수.__doc__
감싸는_함수.__module__ = 원본_함수.__module__
return 감싸는_함수
자동 복사 방식 (권장):
from functools import wraps
def 확장_래퍼(원본_함수):
@wraps(원본_함수) # 모든 메타데이터 자동 복사
def 감싸는_함수(*위치인자, **키워드인자):
"""이 문서도 원본 함수의 것으로 대체됩니다"""
return 원본_함수(*위치인자, **키워드인자)
return 감싸는_함수
다중 데코레이터 적용
여러 데코레이터를 중첩 사용할 때는 적용은 아래에서 위로, 실행은 위에서 아래로 진행됩니다.
from functools import wraps
def 굵게_처리(함수):
@wraps(함수)
def 래퍼(*인자, **키워드):
print("[굵게] 전처리")
값 = 함수(*인자, **키워드)
print("[굵게] 후처리")
return f"<strong>{값}</strong>"
return 래퍼
def 기울임_처리(함수):
@wraps(함수)
def 래퍼(*인자, **키워드):
print("[기울임] 전처리")
값 = 함수(*인자, **키워드)
print("[기울임] 후처리")
return f"<em>{값}</em>"
return 래퍼
적용 예시:
@굵게_처리 # 2단계: 반환값을 굵게 처리
@기울임_처리 # 1단계: 먼저 기울임 처리
def 메시지_생성():
return "안녕하세요"
# 실행 흐름:
# 메시지_생성()
# → 굵게_처리.래퍼() → 기울임_처리.래퍼() → 메시지_생성 원본
# → "<em>안녕하세요</em>" → "<strong><em>안녕하세요</em></strong>"
매개변수를 받는 코레이터
설정값을 전달해야 하는 경우, 함수를 세 단계로 중첩하여 구현합니다.
from functools import wraps
def 인증_검사(저장소_유형="파일"):
"""인증 방식을 지정하는 데코레이터 팩토리"""
def 중간_래퍼(대상_함수):
@wraps(대상_함수)
def 실행_래퍼(*인자, **키워드):
검사기 = {
"파일": "파일 기반 인증 검사 중...",
"MySQL": "MySQL DB 인증 검사 중...",
"SQLite": "SQLite DB 인증 검사 중..."
}
메시지 = 검사기.get(저장소_유형, "알 수 없는 인증 방식")
print(메시지)
return 대상_함수(*인자, **키워드)
return 실행_래퍼
return 중간_래퍼
사용 예시:
@인증_검사(저장소_유형="MySQL")
def 관리자_페이지():
print("관리자 대시보드 표시")
@인증_검사() # 기본값 "파일" 사용
def 일반_페이지():
print("일반 사용자 페이지 표시")
# 평가 과정:
# @인증_검사("MySQL") → 중간_래퍼 획득 → 중간_래퍼(관리자_페이지) → 실행_래퍼
클래스 기반 데코레이터
__call__ 메서드를 구현하면 클래스도 데코레이터로 활용할 수 있습니다.
기본 형태:
class 실행_추적기:
def __init__(self, 대상_함수):
print(f"추적기 초기화: {대상_함수.__name__}")
self._원본 = 대상_함수
def __call__(self, *인자, **키워드):
print("=== 함수 실행 시작 ===")
결과 = self._원본(*인자, **키워드)
print("=== 함수 실행 종료 ===")
return 결과
@실행_추적기
def 데이터_처리(항목):
print(f"{항목} 처리 중")
return 항목.upper()
# 데이터_처리("test") 호출 시:
# 실행_추적기 인스턴스 생성 → __call__ 메서드 호출
매개변수를 받는 클래스 데코레이터:
class 조건부_실행:
def __init__(self, 활성화_여부):
print(f"데코레이터 설정: 활성화={활성화_여부}")
self._활성 = 활성화_여부
self._대상 = None
def __call__(self, 대상_함수):
print(f"함수 등록: {대상_함수.__name__}")
self._대상 = 대상_함수
return self._실행_분기
def _실행_분기(self, *인자, **키워드):
if not self._활성:
print("[비활성] 원본 함수 스킵")
return None
print("[활성] 원본 함수 실행")
return self._대상(*인자, **키워드)
@조건부_실행(활성화_여부=True)
def 중요_작업():
print("핵심 비즈니스 로직")
@조건부_실행(활성화_여부=False)
def 테스트_작업():
print("테스트용 코드")