본문 바로가기
잡지식 저장고/Python

데코레이터란 : Python Decorator 예시집 - 1

by Slate_Knowledge 2023. 3. 5.
728x90

데코레이터?

파이썬에서 데코레이터란, 일반적으로 함수의 앞뒤에 미리 정의해둔 처리를 추가적으로 수행할 수 있게끔 치장하는 역할을 수행하는 컴포넌트이다. 아래와 같은 logger 함수를 데코레이터로 선언한다고 하면,

def logger(function):
    def wrapper(*args, **kwargs):
        """wrapper documentation"""
        print(f"----- {function.__name__}: start -----")
        output = function(*args, **kwargs)
        print(f"----- {function.__name__}: end -----")
        return output
    return wrapper

위의 logger가 하는 일은 3,5 번째 줄에서 볼 수 있듯이 주어진 함수의 시작 및 끝을 print로 알려주는 것이다. 이걸 직접 써보면 아래와 같이 써볼 수 있다.

logger의 두가지 사용법 : wrapper function으로 직접 호출하는 방식과 데코레이터로 사용하는 방식

logger 함수는 한번 정의해두면 그 다음부터는 두가지 방식으로 사용이 가능한데, 아래의 코드블럭처럼 say_hello라는 함수가 있다고 했을 때,

def say_hello():
    """hello documentation"""
    print('hello')
    time.sleep(1)

일반적인 wrapper function으로 사용해서

first_hello = logger(say_hello)
first_hello()

와 같이 사용하거나 아니면,

@logger
def say_hello():
    """hello documentation"""
    print('hello')
    time.sleep(1)
say_hello()

와 같이 사용할 수 있다. 첫번째와 같이 사용하는 경우 함수 이름을 정확하게 override 해주지 않으면 본 예시에서의 logger같이 함수명을 사용하는 wrapper가 적절하게 사용되기 어려운 단점이 생긴다.

@wraps

위의 예시에서 보면, logger 데코레이터는 내부에서 새로 정의하는 wrapper 함수 객체를 선언하고 이를 반환한다. 그렇기 때문에, 데코레이터를 통해서 한번 묶인 함수는 본래 자신의 name이나 document, 그리고 help 호출 시 나오는 변수 리스트 정보를 잃고 만다.

wrapper로 묶였기 때문에 원래의 함수 이름과 docstring을 잃는다.
help 빌트인 함수를 호출했을 때 나오는 설명도 wrapper에 관한것으로 덮어씌워진다.

관련 기타 블로그들을 보았을 때, 디버깅에도 어려움이 생긴다고 하는데, 직접 해봤을때는 wrapper call이 traceback에 추가되는거 말고는 딱히 잘 모르긴 하겠지만 일단 docstring을 잃게 된다는 거 자체로 프로젝트에서 autodoc(sphinx, readthedocs등) 모듈을 사용시 매우 곤란한 문제임은 맞다.

ValueError를 던지게 만들어서 Traceback을 보여주게 만든 코드
정상적으로 traceback이 끝까지 잘 뜨는걸 볼 수 있다.

이를 피할 수 있도록 해주는 빌트인 데코레이터가 바로 @wraps 이다. @wraps 데코레이터는 데코레이터 함수 정의 시 그 내부에 사용하면 되는데, 그러면 타겟함수의 이름이나 docstring을 wrapper에게 그대로 상속시켜준다. 아래와 같이 사용하면 원했던 대로 함수 이름 및 도큐먼트가 잘 상속되는걸 확인할 수 있다.

import time
from functools import wraps

def logger(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        """wrapper documentation"""
        print(f"----- {function.__name__}: start -----")
        output = function(*args, **kwargs)
        print(f"----- {function.__name__}: end -----")
        return output
    return wrapper

@logger
def say_hello():
    """hello documentation"""
    print('hello')
    time.sleep(1)

원본 함수의 name 및 doc
help 까지 잘 상속된 것을 확인 가능

참고원문:

https://towardsdatascience.com/12-python-decorators-to-take-your-code-to-the-next-level-a910a1ab3e99

 

12 Python Decorators To Take Your Code To The Next Level

Do more things with less code without compromising on quality

towardsdatascience.com

https://cjh5414.github.io/wraps-with-decorator/

 

Python decorator에 @wraps를 사용해야 하는 이유

Jihun's Development Blog

cjh5414.github.io

https://stackoverflow.com/questions/308999/what-does-functools-wraps-do

 

What does functools.wraps do?

In a comment on this answer to another question, someone said that they weren't sure what functools.wraps was doing. So, I'm asking this question so that there will be a record of it on StackOverfl...

stackoverflow.com

728x90
반응형

댓글