2021. 6. 27. 22:48

파이썬 데코레이터에서 wraps를 사용해야 하는 이유

파이썬에는 함수를 파라미터로 받아서 함수를 반환하기 위해서 데코레이터를 사용한다. 말하자면 함수의 함수인 셈인데, 파라미터 변환이나 코드 추적(로깅), 유효성 검사 및 재시도 로직 등 이 데코레이터를 사용하면 다양한 작업들을 손쉽게 처리해줄 수가 있다.

이 데코레이터를 사용할 때에는 주의해야할 점이 몇가지 있는데 오늘은 그 주의할 점에 대해서 이야기 하려고 한다.

import logging as logger


def trace_decorator(function):
    def wrapped(*args, **kwargs):
				"""
        decorator docstring
        """
        logger.info("%s 실행", function.__qualname__)
        return function(*args, **kwargs)

    return wrapped


@trace_decorator
def process_account(account_id):
    """
    id별 계정 처리 로그를 보기 위함
    """
    logger.info("%s 계정 처리", account_id)
		# 비즈니스 로직 구현하는 부분 ...

if __name__ == "__main__":
    print(process_account.__qualname__)
>> trace_decorator.<locals>.wrapped
	print(help(process_account))
>> decorator docstring

로그 추적을 위한 데코레이터 사용을 위해 임의의 trace_decorator라는 데코레이터를 달아주었다.

그리고 어떻게 호출되는지 확인해보니 데코레이터가 실제로 원본 함수를 wrapped라 불리는 새로운 함수로 변경했기 때문에 원본 함수의 이름이 아니라 새로운 함수의 이름을 출력하게 된다.

help로 docstring을 불러오더라도 우리가 로그에서 알고 싶어하는 실행된 함수에서가 아니라 데코레이터의 docstring을 가져온다.

이렇게 되면 개별 함수를 확인하고 싶은 경우에 실제 실행된 함수를 알 수 없으므로 오히려 디버깅이 더 어려워지는 문제가 생긴다.

그럴때 필요한 것이 바로 이 @wraps 데코레이터다. 이 데코레이터를 적용해서 실제로는 function으로 파라미터로 받은 함수를 래핑한 것이라고 명시하는 것이다.

import logging as logger
from functools import wraps

def trace_decorator(function):
    @wraps(function)
    def wrapped(*args, **kwargs):
        """
        decorator docstring
        """
        logger.info("%s 실행", function.__qualname__)
        return function(*args, **kwargs)

    return wrapped


@trace_decorator
def process_account(account_id):
    """
    id별 계정 처리 로그를 보기 위함
    """
    logger.info("%s 계정 처리", account_id)


if __name__ == "__main__":
    print(process_account.__qualname__)
>> process_account
    print(help(process_account))
>> id별 계정 처리 로그를 보기 위함

이렇게 wraps로 데코레이터 함수를 감싸주면 정상적으로 우리가 원하는 실행된 함수에서의 docString, 호출된 함수의 이름이 제대로 표시되는 것을 확인할 수 있다.