본문 바로가기

Computer/Python

데코레이터와 functools.wrap

반응형

파이썬은 함수에 적용할 수 있는 데코레이터 (decorator)를 정의하는 특별한 구문을 제공하는데, 데코레이터는 자신이 감싸고 있는 함수가 호출되기 전과 후에 코드를 추가로 실행합니다. 이는 데코레이터가 자신이 감싸고 있는 함수의 입력 인자, 반환 값, 함수에서 발생한 오류에 접근할 수 있다는 뜻으로 함수의 의미를 강화하거나 디버깅, 함수를 등록하는 등의 일에 유용하게 사용할 수 있습니다.

예를 들어 함수가 호출될 때마다 인자 값과 반환 값을 출력하고 싶을때 다음과 같은 "trace" 라는 데코레이터를 정의할 수 있습니다.

def trace(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        print(f'{func.__name__}({args!r}), {kwargs!r}', end = '')
        print(f'-> {result!r}')
        return result
    return wrapper

이 데코레이터를 함수에 적용할때는 @ 기호를 사용하며, @ 기호를 함수에 적용하면 이 함수에 대해 데코레이터를 호출한 후, 데코레이터가 반환한 결과 (wrapper 함수)를 원래 함수의 이름으로 등록하는 것과 같습니다. 따라서 다음과 같이 꾸며진 fibonacci 함수는 데코레이터에서 정의한 코드를 원래의 fibonacci 함수가 실행되기 전과 후에 실행합니다.

@trace
def fibonacci(n):
    '''n번째 피보나치 수를 반환한다.'''
    if n in (0,1):
        return n
    return (fibonacci(n-2) + fibonacci(n-1))
    
result = fibonacci(4)

이 코드는 원하는대로 잘 동작하지만 의도하지 않은 부작용이 발생하는데, 데코레이터가 반환하는 함수의 이름이 fibonacci가 아닌 wrapper 함수가 됩니다. 또한, fibonacci 함수에서 정의한 독스트링이 출력되지 않습니다.

print(fibonacci)
print(help(fibonacci))

이 문제가 발생하는 이유는 trace 함수는 자신의 본문에 정의된 wrapper 함수를 반환하기 때문에 모듈 상에는 이 wrapper 함수가 fibonacci 라는 이름으로 등록되기 때문입니다. 또한 데코레이터가 감싸고 있는 원래 함수의 위치를 찾을 수 없기 때문에 객체 직렬화도 에러가 발생합니다.

import pickle
pickle.dumps(fibonacci)


문제를 해결하는 방법은 functools 내장 모듈에 정의된 wraps 도우미 함수를 사용하는 것으로 wraps 는 데코레이터 작성을 돕는 데코레이터입니다. 다음과 같이 wraps를 원래 fibonacci 함수를 인수로 wrapper에 대해 데코레이터로 적용하면 원래 함수명 및 함수에 대한 설명 등 함수가 가지고 있는 많은 표준 attribute를 유지합니다.

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}', end = '')
        print(f'-> {result!r}')
        return result
    return wrapper

이제 help 함수나 print 함수를 수행하면 데코레이터로 감싸진 함수에 대해서도 원하는 결과를 볼 수 있으며 pickle 객체 직렬화도 제대로 작동합니다.

반응형

'Computer > Python' 카테고리의 다른 글

Property, Setter, Getter  (0) 2021.06.28
concurrent.futures를 이용한 병렬화  (0) 2021.06.27
Public, Private Attributes  (0) 2021.06.27
변수 영역과 클로저  (0) 2021.06.27
스레드 세이프 (Thread-safe)  (0) 2021.06.20