→ 함수의 의미 강화 또는 디버깅, 등록 등에 유용하게 사용됨
def trace(func):
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f'{func.__name__}({args!r}, {kwargs!r}) '
f'-> {result!r}')
return result
return wrapper
trace()
함수는 함수를 인자로 받으며, 내부에 wrapper()
라는 함수를 선언합니다.wrapper()
함수는 trace()
함수의 인자로 넘어온 함수를 호출합니다.wrapper()
함수에서 result
변수에 함수의 리턴값을 저장해두고 리턴을 해주면 함수의 리턴값을 그대로 받아올 수 있습니다.좀 더 깔끔하게 @
기호와 함께 함수 헤더 위에 바로 데코레이터를 선언하는 방법도 있으며 실무에서는 거의 이런 형태로 데코레이터가 사용된다고 합니다.
@trace
def fibonacci(n):
"""n번째 피보나치 수를 반환한다."""
if n in (0, 1):
return n
return (fibonacci(n-2) + fibonacci(n-1))
@
기호의 사용은 함수에 대해 데코레이터를 호출한 후, 데코레이터가 반환한 결과를 원래 함수가 속해야 하는 영역에 원래 함수와 같은 이름으로 등록하는 것과 같습니다.*args
와 **kwargs
를 사용하지 않는다면 예외가 발생합니다.wrapper()
함수가 원래 인자를 무시했기 때문인데요.*args
**kwargs
를 사용해야 합니다.인자를 받는 fibonacci
함수에 데코레이터를 적용하면 정상적으로 작동합니다.
fibonacci = trace(fibonacci)
fibonacci(4)
위 코드는 잘 작동하지만, 데코레이터가 반환하는 함수가 fibonacci
가 아니게 됩니다.
print(fibonacci)
<function trace.<locals>.wrapper at 0x00000138104793F0>
원래 함수의 메타 정보가 데코레이터의 메타 정보로 대체된다는 점인데요. 해당 문제의 해결 방법은 functools 내장 모듈에 정의된 wraps 도우미 함수를 사용하는 것입니다. 이 함수는 데코레이터 작성을 돕는 데코레이터입니다.
wraps를 wrapper 함수에 적용하면 wraps가 데코레이터 내부에 들어가는 함수에서 중요한 메타 데이터를 복사해 적용합니다.
→ 내부 함수 위에 @functools.wraps 데코레이터 선언해서 사용
from functools import wraps
def trace(func):
@wraps(func)
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
print(f'{func.__name__}({args!r}, {kwargs!r}) '
f'-> {result!r}')
return wrapper
@trace
def fibonacci(n):
"""n번째 피보나치 수를 반환한다."""
if n in (0, 1):
return n
return (fibonacci(n-2) + fibonacci(n-1))
help(fibonacci)
import pickle
print(pickle.dumps(fibonacci))
(인트로스펙션이란 실행 시점에 프로그램이 어떻게 실행되는지 관찰하는 것을 의미합니다.)