Language/Python

함수에 적용되어있는 decorator를 알아내는 방법

j4ko 2023. 5. 5. 20:07
728x90
반응형


개요

최근 사내 소스 코드를 보면서 이런 생각을 해봤습니다. "함수에 적용된 decorator를 어떻게 알아낼 수 있을까?"였습니다.  이유는 Decorator를 통해서 Client로 입력받는 값이나 해당 View가 Authenticate을 필요로 하는지의 기능을 구현하곤 하는데요 이 정보들을 활용해 API 문서를 작성하는데 생산성을 끌어올리고자 함이었죠

보통은 문서 자동화를 지원하는 라이브러리를 사용해서 API 문서를 작성하기 때문에 이러한 상황을 맞닥뜨릴 일이 별로 없습니다만 그러한 상황이 아니기도 하며 API 문서를 수기로 작성해 나가는 방식이기 때문에 조사해 볼 가치는 충분했습니다.

그러므로 "함수에 적용된 Decorator를 어떻게 알아낼 수 있을까?"를 탐구한 과정을 기록해보고자 합니다.


"함수에 적용된 Decorator를 어떻게 알아낼 수 있을까"의 의미

우선 "함수에 적용된 Decorator를 어떻게 알아낼 수 있을까?"의 의미를 정확히 설명드리기 위해 Closure와 Decorator가 Python에서 어떻게 작성되는지 보겠습니다. 우선 Closure는 다음과 중첩 함수로 정의된 형태입니다.

def outer(func):
    def inner(*args, **kwargs):
        print("Before")
        result = func(*args, **kwargs)
        print("After")
        return result

    return inner

그리고 decorator는 '@'를 사용하여 다른 함수에 closure를 적용시키는 형태라 말할 수 있습니다.

@outer # decorator
def function01():
    return "Hello"

즉,  "함수에 적용된 Decorator를 어떻게 알아낼 수 있을까?"의 의미를 위 코드의 예시를 통해 설명드리자면 "function()이라는 함수를 통해 outer라는 데코레이터가 적용되어 있음을 어떻게 확인할 수 있을까"를 뜻합니다. 

 

__closure__ 이용하기

Python에서는 Special Method라 정의된 Method가 존재합니다. 이는 class 뿐만 아니라 function에도 똑같이 정의되어 있습니다. 함수에 적용된 Decorator의 정보를 알아내기 위해 가장 먼저 떠올린 Special Method는 __closure__입니다. 

그런데 __closure__는 상황에 맞지 않는 방법입니다. __closure__는 closure를 통해 생성된 결과를 통해 사용할 수 있는 녀석이기 때문입니다. 실제로 function01의 namespace의 __closure__를 열어보면 outer라고 정의된 항목을 찾을 수 없습니다.

print(function01.__closure__) # (<cell at 0x100543640: function object at 0x1004479a0>,)
print(function01.__closure__[0].cell_contents) # <function function01 at 0x1004479a0>

__closure__를 사용하려면 다음과 같은 형태를 가져야 합니다.

foo = outer(function01)
print(foo.__closure__[0].cell_contents)

# OutPut
# <function outer.<locals>.inner at 0x102e77880>

그래서 __closure__를 이용하여 Decorator를 알아내는 방법은 무리입니다.



__wrapped__ 이용하기

관련하여 조사를 계속해보니 __wrapped__라는 Special Method가 존재한다는 것을 알 수 있었습니다. __wrapped__는 데코레이터를 적용한 함수나 메서드에서 원래의 함수나 메서드를 참조하기 위한 속성입니다. Decorator를 적용하기 전의 함수를 참조할 수 있는 것처럼 이해됩니다.

사용 형태는 다음과 같습니다.

def outer(func):
    @wraps(func)
    def inner(*args, **kwargs):
        print("Before")
        result = func(*args, **kwargs)
        print("After")
        return result

    return inner
    

@outer
def function01():
    return "Hello"

    
foo = function01.__wrapped__
print(foo) # <function function01 at 0x104ea79a0>

function01을 통해 접근한 __wrapped__라는 Special Method의 결괏값이 functin01을 가리키고 있으니 이 방법도 무리인 듯합니다.

__globals__

 closure와 decorator에 관련된 __closure__나 __wrapped__ Special Method로는 함수에 정의된 decorator의 정보를 알아낼 수 없었습니다.  권장되지 않는 방법이지만 함수의 Global NameSpace를 통해 적용된 decorator를 알아낼 수 있지 않을까 싶었습니다. 이를 지원하는 Special Method는 __globals__입니다.

{'__annotations__': {},
 '__builtins__': <module 'builtins' (built-in)>,
 '__cached__': None,
 '__doc__': None,
 '__file__': '/Users/jako/private/git-repo/study-python-src/notepad02.py',
 '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x100d911b0>,
 '__name__': '__main__',
 '__package__': None,
 '__spec__': None,
 'function01': <function function01 at 0x100ccf880>,
 'outer': <function outer at 0x100d50040>, # 있긴 하네..?
 'pprint': <function pprint at 0x100ea6a70>,
 'wraps': <function wraps at 0x100e4cf70>}

Gobal NameSpace를 통해 접근하니 적용된 decorator가 보이긴 합니다. 목적 자체는 달성했지만 decorator 하나를 참조하기 위해 Global NameSpace를 열어서 확인하다는 것은 다소 과장되어 보입니다. 하여 이 방법은 그다지 사용하고 싶단 생각은 들지 않았습니다.



그래서 결론은?

사실 이런저런 조사에도 불구하고 함수에 적용된 decorator를 알아내는 방법은 찾지 못했습니다. 꼼수를 부려 구현하더라 함수에 적용된 decorator를 알아내기 위해선 이를 추적하는 기능을 직접 구현해야 하는 상황인 듯합니다. 혹시 이를 알아내는 방법이 있다면 댓글로 남겨주시면 감사하겠습니다

728x90
반응형