본문 바로가기
Python/기초

[파이썬/Python] 이해하기 쉬운 데코레이터 원리

by 모두의 케빈 2023. 7. 11.

데코레이터(Decorator)란?


 

함수/메서드의 기능을 확장하거나 변경해 주는 특별한 문법

 

파이썬 코드를 공부하다 보면, '@' 골뱅이가 붙은 특이한 이름을 보신 적이 있으실 겁니다. 파이썬에서 골뱅이(@)는 데코레이터임을 의미하는 약속어 같은 표시입니다.

데코레이터란 직역 하면 장식하는 주체로, 함수나 메서드에 적용되어 기능을 추가, 확장, 변경하는 역할을 합니다. 데코레이터는 함수를 인자로 받고, 또 다른 함수를 반환하는 고차함수(higher-order function)입니다.

데코레이터를 사용하면 코드의 길이를 단축할 수 있고, 개념을 아는 사람들은 직관적으로 파악할 수 있어서 가독성을 높여주는 기능을 합니다. 뿐만 아니라, 함수의 핵심 기능에 집중할 수 있도록 도움을 주기도 합니다.

 

 

데코레이터 기초: 직접 구현해 보기 (인자가 없는 경우)


데코레이터, 이것만 기억하자: "함수를 입력받아, 함수를 반환"

 

우선 인자가 없는 두 개의 함수로 데코레이터의 기초를 실습해 보겠습니다. 첫 번째 함수는 "Good_night" 함수입니다. "좋은 꿈 꾸렴."이라는 문자열을 출력합니다.

def Good_night():
    print("좋은 꿈 꾸렴")
    
Good_night()

# 위 코드의 결과값
좋은 꿈 꾸렴

 

두 번째 함수는 "Love_you"라는 함수입니다. "잘 자요, 내 사랑."을 출력하고 인자로 받은 함수를 실행한 다음, "꿈에서 봐요."를 출력합니다. "잘 자요, 내 사랑" → "좋은 꿈 꾸렴." → "꿈에서 봐요."를 순차적으로 출력하기 위해서 다음과 같이 함수를 인자로 함수를 호출할 수 있습니다.

def Love_you(fn): # 함수 fn을 인자로 받는다.
    print("잘자요, 내 사랑.")
    fn() # 인자로 받은 함수 fn을 호출
    print("꿈에서 봐요.")
    
    
Love_you(Good_night)

# 위 코드의 결과값
잘자요, 내 사랑.
좋은 꿈 꾸렴
꿈에서 봐요.

 

위의 "Love_you"라는 함수는 함수를 인자로 받습니다. Love_you 함수를 조금 수정하여 함수를 반환하도록 바꿔주면 데코레이터로서 활용할 수 있습니다. 기억합시다. "데코레이터는 함수를 인자로 받아서, 함수를 반환한다."

Love_you에서 수행되는 모든 기능을 print_fn이라는 함수로 정의했습니다. 그리고 이를 return 하도록 코드를 수정했습니다. 데코레이터는 적용하고자 하는 함수/메서드 위에 @함수/메서드명으로 정의합니다.

Good_night 함수 위에 Love_you 함수를 데코레이터로 정의해서 사용해 보겠습니다. Love_you는 함수를 인자로 받고, 함수를 반환하는 구조이므로 데코레이터로 활용할 수 있습니다.

def Love_you(fn): # 함수 fn을 인자로 받는다.
    def print_fn():
        print("잘자요, 내 사랑.")
        fn() # 인자로 받은 함수 fn을 호출
        print("꿈에서 봐요.")
    return print_fn

@Love_you
def Good_night():
    print("좋은 꿈 꾸렴")
    
Good_night()

# 위 코드의 실행 결과
잘자요, 내 사랑.
좋은 꿈 꾸렴
꿈에서 봐요.

 

 

 

데코레이터 심화: 데코레이터로 인자를 함께 전달


*args, **kwargs를 기억하자.

 

두 숫자를 더한 값을 출력하는 함수와, 그 함수의 결과값을 보기 좋게 꾸며주는 함수 두 개를 만들어보겠습니다. 꾸며주는 함수를 데코레이터로 사용하기 위해서는 더해주는 함수를 인자로 받아야 합니다. 이때, 더해주는 함수를 실행하기 위해서는 두 개의 인자가 필요하기 때문에 데코레이터로 인자를 함께 전달해야 합니다.

데코레이터로 인자를 함께 전달하기 위해서는 *args와 **kwargs를 활용하면 됩니다. 데코레이터가 return 할 함수에 *args와 **kwargs를 인자로 넣어주어야 하며, 그 외 나머지 기본적인 구조는 동일합니다.

def deco_answer(fn):
    def decorate_your_answer(*args, **kwargs):
        print(f"{fn.__name__} 함수를 시작합니다.")
        print("계산 중 ... ")
        print(f"실행결과: {fn(*args, **kwargs)} 입니다.")
    return decorate_your_answer

@deco_answer
def add_value(a,b):
    return a+b
   
add_value(5,10)

# 위 코드의 실행 결과
add_value 함수를 시작합니다.
계산 중 ... 
실행결과: 15 입니다.

 

데코레이터 심화 2: 데코레이터로부터 값 return 받기 & @wraps

 

데코레이터로부터 값을 return 받고 싶을 때는, 데코레이터가 함수를 return 하기 전에 return 명령어를 한번 더 사용해 주면 됩니다. 이렇게 되면, 데코레이터도 실행되고 데코레이터로부터 값도 return 받을 수 있습니다.

아래 예시를 보면 이해가 되실 겁니다.

add_value 함수를 데코레이터로 전달하여, 처리하고 그 결과값을 'return_from_decorator' 변수에 할당했습니다. return_from_decorator에는 데코레이터에서 return 하도록 설정한 문자열이 들어가 있는 것을 확인할 수 있습니다.

from functools import wraps

def deco_answer(fn):
    @wraps(deco_answer)
    def decorate_your_answer(*args, **kwargs):
        print(f"{fn.__name__} 함수를 시작합니다.")
        print("계산 중 ... ")
        print(f"실행결과: {fn(*args, **kwargs)} 입니다.")
        return "데코레이터로부터 return합니다."    # 데코레이터로부터 결과값 반환받기
    return decorate_your_answer

@deco_answer
def add_value(a,b):
    return a+b
   
return_from_decorator = add_value(5,10)
print(return_from_decorator)


# 위 코드의 결과값
add_value 함수를 시작합니다.
계산 중 ... 
실행결과: 15 입니다.
데코레이터로부터 return합니다.

 

참고로 데코레이터는 함수를 주고받기 때문에, 이 과정에서 원래 함수의 메타 정보가 데코레이터의 메타 정보로 대체될 가능성이 있습니다. 이 가능성을 미연에 차단하기 위해서는 @wraps 데코레이터를 선언해 주면 됩니다. (@wraps 데코레이터는 functools의 wraps 모듈에 있습니다.)

 

 

댓글