본문 바로가기
Language/Python

[Python] 데코레이터(Decorator)

by 며루치꽃 2021. 11. 17.

데코레이터는 함수를 받아서 새로운 함수를 만들어 반환하는 함수로 정의할 수 있다.

 

데코레이터를 사용하려면 다음과 같은 내용이 선행되어야 합니다.

  • 클로저
  • 일급 함수
  • 가변 인자
  • 인자 언패킹(argument unpacking)

데코레이터의 장점

  1. 중복 제거, 코드 간결, 공통 기능 작성
  2. 로깅, 프레임워크, 유효성 체크 등을 공통적으로 실행할 수 있습니다. 예를 들어 함수의 실행시간을 측정하려고 하면 함수 사이에 넣는 것이 아닌 시작시간, 종료시간을 측정해주는 함수를 데코레이터(Decorator)로 만들어 놓고 나머지 함수에 장식(Decorate)를 하는 것 입니다. 함수 실행 시간 측정을 100개의 함수를 측정한다고 가정할 때, 데코레이터를 통해서 실행되기 때문에 공통 기능으로 실행시킬 수 있습니다.
  3. 조합해서 사용이 용이합니다.

실행시키는 함수를 만들겠습니다.

import time

def perf_clock(func):
    def perf_clocked(*args):
        # 함수 시작 시간 
        st = time.perf_counter() 
        result = func(*args)
        # 함수 종료 시간 계산
        et = time.perf_counter() - st 
        # 실행 함수명
        name = func.__name__
        # 함수 매개변수 
        arg_str = ', '.join(repr(arg) for arg in args)
        # 결과 출력
        print('[%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result)) 
        return result 
    return perf_clocked

외부함수에 내부함수가 있는 클로저 형태에서, func 함수를 매개변수로 받고 outer function에서 넘어온 인자를 갖고 있는 형태입니다. 함수에서 언패킹을 해서 받는 구조입니다. result는 내부함수에서 외부함수의 변수를 참조하고 있습니다. 실행되는 함수가 변경이 되더라도 내부에서 실행이 되는 구조입니다. *args 는 내부함수에서 패킹해서 받고 있습니다.

해당 함수로 인해 모든 함수를 실행할 때마다 성능을 체크할 수 있습니다. 현재에서는 데코레이터로 볼 수 없지만 클로저 형태로 만들었습니다. 이를 데코레이터로서 사용할 수 있습니다. 

 

def time_func(seconds):
    time.sleep(seconds)
    
###
def perf_clock(time_func):
    def perf_clocked(*args):
        # 함수 시작 시간 
        st = time.perf_counter() 
        result = time_func(*args)
        # 함수 종료 시간 계산
        et = time.perf_counter() - st 
        # 실행 함수명
        name = func.__name__
        # 함수 매개변수 
        arg_str = ', '.join(repr(arg) for arg in args)
        # 결과 출력
        print('[%0.5fs] %s(%s) -> %r' % (et, name, arg_str, result)) 
        return result 
    return perf_clocked

만약 time_func(seconds) 가 실행된다고 가정하면, 아래와 같이 실행됩니다.

 

데코레이터를 사용하지 않았다면 위의 함수를 다음과 같이 실행할 수 있습니다.

none_deco1 = perf_clock(time_func)
print(none_deco1, none_deco1.__code__.co_freevars) # [1.50032s] time_func(1.5) -> None

print('-' * 40, 'Called None Decorator -> time_func')
print()
none_deco1(1.5) # [0.00000s] sum_func(100, 150, 250, 300, 350) -> 1150

 

데코레이터를 사용하면 다음과 같이 적용할 수 있습니다.

데코레이터를 사용할 수 있는 방법은 함수 위에 만든 함수를 데코레이트 해주면 됩니다.

@perf_clock
def time_func(seconds):
    time.sleep(seconds)

이 함수가 호출되기 전에 장식이 되어있기 때문에 장식된 함수가 실행이 되면서, 바로 실행이 됩니다.

print('*' * 40, 'Called Decorator -> time_func') # [1.50229s] time_func(1.5) -> None
print()
time_func(1.5)

데코레이터를 사용하지 않았을 때와 같이 결과를 확인할 수 있습니다. 데코레이터를 사용한다면 만들어낸 함수 자체를 실행시키면 됩니다. 데코레이터가 되어 있어 인자가 넘어가서 함수가 실행되고 원하는 공통 기능을 하는 함수를 만들어 로깅, 프레임워크, 유효성 체크 등 공통 기능을 하는 데코레이터 1개로 사용할 수 있습니다.

 

참고

- 인프런 중급 강의

댓글