데코레이터는 감싼 함수가 실행 되기 전과 후에 다른 코드를 실행 할 수 있게하고, 재사용 가능한 빌딩 블록을 정의하며, 그 빌딩 블록으로 다른 함수의 동작을 변경하거나 확장할 수 있는 python의 유용한 기능입니다.
하지만, 단점이 있다면 디버깅이 어렵고 기존(감싸지는) 함수의 메타데이터를 숨겨버리는 단점이 있습니다. 메타 데이터로써 원래 함수명, docstring, 매개변수 리스트는 클로저(closure)에 의해 숨겨집니다.
Meta data를 잃어버리는 데코레이터(Decorator)
아래 코드는 인자로 전달 받은 함수(Python에서 함수는 일급 객체) 의 반환 값을 대문자(uppercase)로 변환하여 반환하는 데코레이터(decorator) 를 작성하고, hello_world 메소드를 데코레이터(decorator)로 감싼 형태 입니다. 그리고 hello_wolrd 메소드는 docstring이 작성되어 있습니다.
하지만, 작성한 hello_world 메소드의 메타 정보를 확인하기 위해 __name__, __doc__ 던더(double underscore) 메소드를 사용해 보면 아래와 같이 기존 함수의 메타데이터는 wrapper에 의해 숨겨지고 확인할 수 없게 됩니다. 이러한 문제가 발생하는 이유는 데코레이터(decorator)로 감싸질 때 기존 함수의 이름은 'wrapper'가 되고, 원래 docstring을 잃어버리기 때문입니다.
functools.wraps()
이 문제를 해결하기 위해 functools 모듈의 wraps 함수를 사용합니다. 이는 wrapper 함수를 정의할 때 함수 데코레이터(decorator)로 update_wrapper() 를 호출하기 위한 편의 함수 (아래 참고) 입니다. 사용법은 아래 코드와 같이 wrapper를 감싸고 인자로 func를 전달합니다.
hello_world의 메타데이터를 조회하면 기존에 각각 'wrapper' 와 None으로 반환되던 __name__, __doc__이 원래 함수의 내용으로 update 되어 보여지는 것을 확인 할 수 있습니다.
update_wrapper
python 공식문서의 functools 를 보면, wraps는 update_wrapper 를 사용하기 위한 '편의 함수'라는 표현이 있습니다. update_wrapper가 감싸지는 함수의 메타데이터를 불러올 수 있는 이유는 _wrapped 함수처럼 보이도록 wrapper 함수를 갱신_하기 때문입니다.
내부 검사와 기타 목적을 위해 원래 함수에 엑세스 할 수 있도록, __wrapped__ 어트리뷰트를 wrapper에 자동으로 추가 합니다. 만약, wrapper 함수가 갱신되지 않으면, 반환된 함수의 메타 데이터는 원래 함수가 아닌 wrapper를 따르기 때문에 메타 데이터를 '잃어버린' 것과 같아집니다.
아래 코드로 부터, update_wrapper 를 사용했을 때와 그렇지 않을 때, hello_world 에 __wrapped__ 속성이 추가되고 추가되지 않음을 확인 할 수 있습니다.
참고
https://docs.python.org/ko/3/library/functools.html#functools.update_wrapper
https://github.com/python/cpython/issues/61684