🎀

Decorator 내부 구조 — @가 실제로 하는 일

함수를 받아서 함수를 반환하는 함수 — 그게 전부다

@my_decorator
def greet():
    return 'hello'

# 위 코드는 이것과 완전히 동일하다:
def greet():
    return 'hello'
greet = my_decorator(greet)

@는 특별한 마법이 아니다. 함수를 인자로 받아서 새 함수를 반환하는 함수를 적용하는 문법적 편의일 뿐.

기본 구조

def my_decorator(func):
    def wrapper(*args, **kwargs):
        print('before')
        result = func(*args, **kwargs)
        print('after')
        return result
    return wrapper

wrapper가 클로저다. 외부 함수(my_decorator)의 변수(func)를 캡처해서 보존한다. *args, **kwargs로 원본 함수가 어떤 시그니처든 받을 수 있게 한다.

functools.wraps가 왜 필요한가

decorator를 적용하면 greet.__name__'wrapper'가 된다. 원본 함수의 메타데이터(이름, docstring, 시그니처)가 사라진다. @functools.wraps(func)를 wrapper에 달면 원본 메타데이터를 복사해준다.

인자가 있는 decorator

def repeat(n):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)  # repeat(3)이 먼저 실행 → decorator 반환 → decorator(func) 실행
def say_hello():
    print('hello')

3중 중첩이 된다. repeat(3)decorator를 반환하고, decorator(say_hello)wrapper를 반환한다. FastAPI의 @app.get('/path')도 이 패턴.

클래스 decorator

__call__ 메서드가 있는 클래스도 decorator로 쓸 수 있다. 상태를 유지해야 하는 경우(호출 횟수 카운팅 등)에 유용하다.

핵심 포인트

1

@decorator는 func = decorator(func)의 문법적 설탕

2

wrapper 함수가 클로저로 원본 func을 캡처

3

functools.wraps(func)로 원본 메타데이터 보존 필수

4

인자 있는 decorator는 3중 중첩: outer(args) → decorator(func) → wrapper(*args)

사용 사례

로깅 — 함수 호출 시점/인자/반환값 자동 기록 FastAPI — @app.get("/path")로 라우팅 등록