C.W.K.
Stream
Lesson 02 of 05 · published

functools.wraps — 메타데이터 보존

~15 min · wraps, functools, metadata, introspection

Level 0호기심
0 XP0/93 lessons0/23 achievements
0/100 XP to next level100 XP to go0% complete

처음 짤 때 만나는 버그

lesson 1 의 순진한 decorator 에는 문제 — 감싸진 함수가 이름, docstring, 시그니처 잃어. hello.__name__ 이 이제 'wrapper'. help(hello) 가 wrapper 의 docstring 표시, 원래 거 X. 함수 introspect 하는 도구 — 디버거, 문서 생성기, inspect.signature — 가 wrapper 보고 진짜 함수 못 봄.

functools.wraps — 메타데이터 복사

해결책 한 줄 — wrapper 에 @functools.wraps(fn). __name__, __doc__, __module__, __qualname__, __annotations__, __wrapped__ 를 원래 함수에서 wrapper 로 복사. 이제 help(hello) 가 원래 docstring 표시, hello.__name__'hello'.

__wrapped__ — 원래 거에 접근

wraps 가 추가하는 한 가지 — 원래 함수 가리키는 __wrapped__ 속성. inspect.signatureinspect.unwrap 이 이거 사용. wrapper 우회하고 원래 거 호출해야 하면 wrapped_fn.__wrapped__().

원칙: wrapper 에 항상 @functools.wraps. 진짜 단점 없고, 빼면 introspection 깨짐 — 디버깅 어려운 방식으로. 근육 기억 만들어.

wraps 가 보존 *못* 하는 것

wrapper 의 *시그니처* 는 바이트코드 레벨에선 여전히 (*args, **kwargs). inspect.signature(hello)__wrapped__ 덕분에 작동, 근데 wrapper 의 실제 __code__ 는 여전히 뭐든 받아. 대부분 목적엔 OK. 중요한 경우 (wrapper 레벨에서 엄격한 인자 검증) 는 드물어.

Code

wraps 없이 — 메타데이터 잃음·python
def announce(fn):
    def wrapper(*args, **kwargs):
        """이건 wrapper 의 docstring."""
        print("calling", fn.__name__)
        return fn(*args, **kwargs)
    return wrapper

@announce
def hello(name):
    """이름으로 인사."""
    return f"hi {name}"

print(hello.__name__)        # 'wrapper'   <- 잃음
print(hello.__doc__)         # '이건 wrapper 의 docstring.'   <- 잃음
functools.wraps 로 — 메타데이터 보존·python
import functools

def announce(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        print("calling", fn.__name__)
        return fn(*args, **kwargs)
    return wrapper

@announce
def hello(name):
    """이름으로 인사."""
    return f"hi {name}"

print(hello.__name__)        # 'hello'      ✓
print(hello.__doc__)         # '이름으로 인사.'   ✓

# 보너스 — __wrapped__ 가 원래 함수 가리킴
print(hello.__wrapped__("pippa"))   # 'hi pippa'  — wrapper 우회
inspect.signature 정확히 작동·python
import functools
import inspect

def trace(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        return fn(*args, **kwargs)
    return wrapper

@trace
def compute(x: int, y: int = 5) -> int:
    """x + y 반환."""
    return x + y

# wraps + __wrapped__ 덕분에 시그니처 정확
print(inspect.signature(compute))    # (x: int, y: int = 5) -> int
print(compute.__doc__)               # 'x + y 반환.'
print(compute.__annotations__)       # {'x': int, 'y': int, 'return': int}
패턴 — 모든 decorator 가 이렇게 생겨야·python
import functools

def my_decorator(fn):
    @functools.wraps(fn)               # <- 항상 이거
    def wrapper(*args, **kwargs):
        # ... 호출 전 로직 ...
        result = fn(*args, **kwargs)
        # ... 호출 후 로직 ...
        return result
    return wrapper

External links

Exercise

lesson 1 의 count_calls decorator 에 @functools.wraps 추가. (a) __name__, __doc__ 보존되는 거, (b) inspect.signature 여전히 정확, (c) wrapped_fn.__wrapped__ 가 원래 함수 반환 — 보여. 그 다음 wraps *없이* 두 번째 버전 만들어서 repr()help() 출력 차이 보여.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.