elevne's Study Note

Python Basics (3) 본문

Backend/Python + FastAPI

Python Basics (3)

elevne 2022. 11. 25. 23:26

저번 시간에 이어서 Decorator에 대해서 더 알아보았다. 

 

 

 

한 함수에 2 개 이상의 Decorator을 사용하는 것이 가능하다고 한다. 아래와 같이 코드를 작성할 수 있는 것이다.

 

 

 

import time
import datetime

def runtime(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print("{} FUNCTION RUN TIME: ".format(func.__name__)+str(end - start))
        return result
    return wrapper

def logger(func):
    def wrapper(*args, **kwargs):
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        print("{} args : {}, kwargs {}".format(timestamp, args, kwargs))
        return func(*args, **kwargs)
    return wrapper

@runtime
@logger
def main(delay_time):
    time.sleep(delay_time)

if __name__ == "__main__":
    main(3)

 

 

출력결과

 

 

 

Decorator을 여러 개 사용할 때는 함수의 선언부터, 위로 한 개씩 작성하는 것이 규칙이라고 한다. 함수에 가까이 있는 것부터 실행되기 때문이다. 위의 출력결과도 runtime() 함수의 결과가 먼저 출력되어 있는 것을 확인할 수 있다.

 

 

 

그런데 위 출력결과를 보게되면 함수 이름 부분에 main 이 아니라 wrapper로 들어가있는 것을 확인할 수 있다. Decorator을 하나만 사용하게 되면 main으로 나왔을텐데 이렇게 나온 이유는 무엇일까? 그 이유는, 순서대로 main 함수가 logger로, main을 받은 logger 함수가 runtime 함수로 들어가기 때문이다. 여기서 logger 함수의 반환값은 Closure 내부에 구현된 wrapper 함수이기 때문에 출력결과에 main이 아닌 wrapper이 나오게 되는 것이다.

 

 

 

위 문제를 해결하기 위해서 functools 모듈의 wraps라는 기능을 사용할 수 있다고 한다. 위 코드에 몇 가지만 추가해주면 된다. 코드는 아래와 같다.

 

 

 

import time
import datetime
from functools import wraps

def runtime(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print("{} FUNCTION RUN TIME: ".format(func.__name__)+str(end - start))
        return result
    return wrapper

def logger(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
        print("{} args : {}, kwargs {}".format(timestamp, args, kwargs))
        return func(*args, **kwargs)
    return wrapper

@runtime
@logger
def main(delay_time):
    time.sleep(delay_time)

if __name__ == "__main__":
    main(3)

 

 

출력결과

 

 

 

functools 모듈의 wraps Decorator을 사용하면 Closure에서 반환되는 객체의 속성이 wrapper 함수의 속성으로 표시되지 않도록 내부적으로 처리한다고 한다. 실행결과를 보면 main으로 잘 출력이 되는 것을 확인할 수 있다. 

 

 

위처럼 여러 Decorator을 사용하는 것이 아니라 하나의 Decorator을 사용하더라도 문제가 되는 경우들도 있다고 한다. 또한, 본인이 구현한 Decorator을 다른 사람이 여러 개의 Decorator과 겹쳐서 사용할 수 있기 때문에 Decorator을 작성할 때는 항상 내부 함수에 wraps Decorator을 선언해주는 것이 좋다고 한다.

 

 

 

지금까지는 함수에만 Decorator을 적용해보았는데, Decorator은 Class에도 사용할 수 있다. 아래와 같이 코드를 작성해줄 수 있다.

 

 

 

import time
from functools import update_wrapper

class Runtime:
    def __init__(self, f):
        self.func = f
        update_wrapper(self, self.func)

    def __call__(self, *args, **kwargs):
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print("{} FUNCTION RUN TIME: {}".format(self.func.__name__, end - start))
        return result

@Runtime
def main(delay_time):
    time.sleep(delay_time)

if __name__ == "__main__":
    main(3)

 

 

출력결과

 

 

 

Class로 Decorator을 만들 때에는 Decorator로 만들 Class에 __call__ 메서드를 정의해주고 로직을 작성하면 된다. 여기서 함수의 경우와 다른 점은, 우서 __call__ 메서드가 Closure 형태가 아니라는 점이다. 또, wraps Decorator을 사용하지 않고 update_wrapper 함수를 사용한다. Class의 경우에는 update_wrapper 가 wraps 의 역할을 하는 것이다.

'Backend > Python + FastAPI' 카테고리의 다른 글

Python Basics (5)  (0) 2022.12.18
Python Basics (4)  (0) 2022.11.27
Python Basics (2)  (0) 2022.11.23
Python Basics (1)  (1) 2022.11.22
Plotly 시작해보기 (2)  (0) 2022.11.05