본문으로 건너뛰기
개발 머꼬
개발 노트Python
hohyeon.dev14

데코레이터를 씌웠더니 함수 이름과 docstring이 사라진 이유

  • #Decorators
  • #Engineering Note
  • #Python

문제 발생

로깅용 데코레이터를 만들어 여러 함수에 적용했더니, 디버거나 문서 생성 도구에서 함수 이름이 전부 wrapper로 나오고 docstring도 사라졌습니다.

def log_call(func):
    def wrapper(*args, **kwargs):
        print(f"calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_call
def greet(name):
    """인사를 반환한다."""
    return f"hello {name}"

print(greet.__name__)  # 'wrapper' (기대: 'greet')
print(greet.__doc__)   # None (기대: '인사를 반환한다.')

원인 분석

@log_callgreet = log_call(greet)와 동일합니다. log_call은 내부에서 정의한 wrapper 함수를 반환하므로, 데코레이터 적용 후 greet이 실제로 가리키는 객체는 원래 greet 함수가 아니라 wrapper 함수입니다. wrapper 함수는 자기 자신의 __name__('wrapper'), __doc__(None), __module__ 등을 갖고 있으므로 원래 함수의 메타데이터가 가려집니다.

이건 디버깅, 로깅, 자동 문서화 도구, help() 호출 등에서 실제로 문제가 됩니다 — 어떤 함수가 호출됐는지 이름으로 추적하는 로그가 전부 wrapper로만 찍히면 원인 추적이 어려워집니다.

해결 방안

표준 라이브러리 functools.wraps를 wrapper 함수에 데코레이터로 붙입니다. wraps는 원래 함수(func)의 __name__, __doc__, __module__, __wrapped__ 등의 메타데이터를 wrapper 함수로 복사해줍니다.

import functools

def log_call(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print(f"calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@log_call
def greet(name):
    """인사를 반환한다."""
    return f"hello {name}"

print(greet.__name__)  # 'greet'
print(greet.__doc__)   # '인사를 반환한다.'

functools.wraps가 추가로 설정하는 __wrapped__ 속성은 원래 함수를 가리키므로, inspect.signature() 같은 도구가 데코레이터를 "투과"해서 원래 함수의 시그니처를 읽을 수 있게 해줍니다. 직접 만드는 모든 데코레이터에는 기본적으로 functools.wraps를 붙이는 것을 습관화하는 편이 안전합니다.

공식 문서

마지막 수정

좋아요북마크

댓글0

아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.