[Python] 함수 데코레이터 정의 (functools.wrap)

미남로그·2023년 1월 10일
0

데코레이터

  • 함수에 적용할 수 있는 데코레이터(decorator)를 정의할 수 있음
  • 자신이 감싸고 있는 함수의 입력 인자, 반환 값, 오류에 접근 가능

→ 함수의 의미 강화 또는 디버깅, 등록 등에 유용하게 사용됨

데코레이터로 원래 함수의 인자 그대로 넘기는 방법

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 를 사용하지 않는다면 예외가 발생합니다.
  • 그 이유는 trace 내부의 wrapper() 함수가 원래 인자를 무시했기 때문인데요.
  • 원래 함수에서 넘어온 인자를 그대로 데코레이터의 내부 함수로 넘기려면 *args
    와 **kwargs를 사용해야 합니다.

인자를 받는 fibonacci 함수에 데코레이터를 적용하면 정상적으로 작동합니다.

fibonacci = trace(fibonacci)
fibonacci(4)
  • 이렇게 꾸며진 함수(새로운 fibonacci)는 wrapper의 코드를 원래의 fibonacci 함수가 실행되기 전과 후에 실행됩니다.
  • 따라서 wrapper는 재귀 스택의 매 단계마다 함수의 인자와 반환 값을 출력합니다.

위 코드는 잘 작동하지만, 데코레이터가 반환하는 함수가 fibonacci 가 아니게 됩니다.

print(fibonacci)
<function trace.<locals>.wrapper at 0x00000138104793F0>
  • trace 함수는 자신의 본문에 정의된 wrapper 함수를 반환합니다.
  • 데코레이터로 인해 이 wrapper 함수가 모듈에 fibonacci라는 이름으로 등록됩니다.

원래 함수의 메타 정보가 데코레이터의 메타 정보로 대체된다는 점인데요. 해당 문제의 해결 방법은 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)
  • 이제 help 함수를 호출하면 데코레이터로 감싸진 함수에 대해서도 원하는 결과를 볼 수 있습니다.
import pickle

print(pickle.dumps(fibonacci))
  • pickle 객체 직렬화도 제대로 작동합니다.

요약

  1. 파이썬 데코레이터는 실행 시점에 함수가 다른 함수를 변경할 수 있게 해주는 구문입니다.
  2. 데코레이터를 사용하면 디버거 등 인트로스펙션을 사용하는 도구가 잘못 작동할 수 있습니다.

(인트로스펙션이란 실행 시점에 프로그램이 어떻게 실행되는지 관찰하는 것을 의미합니다.)

  1. 직접 데코레이터를 구현할 때 인트로스펙션에서 문제가 생기지 않길 바란다면 functools 내장 모듈의 wraps 데코레이터를 사용합니다.

자료 참고 출처

profile
미남이 귀엽죠

0개의 댓글