데코레이터를 씌웠더니 함수 이름과 docstring이 사라진 이유
문제 발생
로깅용 데코레이터를 만들어 여러 함수에 적용했더니, 디버거나 문서 생성 도구에서 함수 이름이 전부 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_call은 greet = 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
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.