Python 데코레이터 완벽 가이드

데코레이터의 핵심 개념

데코레이터는 기존 함수의 동작을 변경하지 않고도 새로운 기능을 주입할 수 있는 고급 문법입니다. 함수를 인자로 받아 기능을 확장한 뒤, 확장된 함수를 반환하는 구조로 동작합니다.

파이썬에서 @ 기호를 통해 데코레이터를 적용하면, 함수 정의 시점에 자동으로 래핑이 이루어집니다.

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("테스트용 코드")

태그: python decorator functools Closure Callable

7월 15일 03:33에 게시됨