Programming,  Python

[Python] Decorator

어떤 함수를 수행하기에 앞서 반복되는 동작을 수행시키고 싶을 때 decorator 기능을 사용한다. 예를 들면 특정 함수의 성능을 측정하고 싶을 때 수행 앞뒤에 시간측정이 필요 할 것이다. 이럴 때 decorator 기능을 사용하면 유용하다.

def big_number(n):
    return n ** n ** n

def make_func_alarm(func):
    def new_func(*args, **kwargs):
        print("함수를 시작합니다.")
        result = func(*args, **kwargs)
        print('함수를 종료합니다.')
        return result
    return new_func

new_func = make_func_alarm(big_number)
new_func(6)
함수를 시작합니다.
함수를 종료합니다.
265911…

위에 make_func_alarm 함수 구조를 보면 함수 내부에 closure인 new_func가 존재한다.

코드 설명을 해보자면, 함수 선언부 밖 에 new_funcmake_func_alarm의 return인 closure가 new_func이 될 것이다. 즉 외부의 new_func은 입력 인수인 big_number 함수 객체가 assign 된다.

Closure 함수 내부를 보면 result = func(*args, **kwargs) 라고 되있기 때문에 가변인자와 가변 키워드 인자로 함수의 입력을 받는다. 위 예제 코드에선 6이 입력으로 들어오면서 해당 값이 big_number의 인자로 들어간다.

import time

def make_time_checker(func):
      def new_func(*args, **kwargs):
            start_time = time.perf_counter()
            result = func(*args, **kwargs)
            end_time = time.perf_counter()
            print('실행시간:', end_time - start_time)
            return result
      return new_func

new_func = make_time_checker(big_number)
new_func(7)
실행시간: 0.14005279900004552
37598….

일반적으로 함수의 앞뒤에 시간을 측정하기 위해 위 코드를 사용하면 유용하다.

Python에선 이런 decorator를 특정 키워드 @를 통해 사용한다. 실제 위 decorator가 사용된 코드를 다음과 같이 변경 할 수 있다.

import time

def make_time_checker(func):
    def new_func(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        print('실행시간:', end_time - start_time)
        return result
    return new_func

@make_time_checker
def big_number(n):
      return n ** n ** n
# Same with the below
# big_number = make_time_checker(big_number)
big_number(7)

시간을 재는 것 뿐만 아니라 trace log를 남기는 기능 또한 필요하다. 예를 들면 함수 수행 전과 수행 후의 log를 찍어야할 경우가 있다. 이럴 때 아래 코드를 사용하면 유용하다.

enable_tracing = True
if enable_tracing:
    debug_log = open("debug.log","w")
    
def trace(func):
    if enable_tracing:
        def callf(*args,**kwargs):
            debug_log.write("Calling %s: %s, %s\n"%
                           (func.__name__,args,kwargs))
            r = func(*args,**kwargs)
            debug_log.write("%s returned %d\n"%
                           (func.__name__,r))
            return r
        return callf
    else :
        return func
    
@trace
def square(x):
    return x*x

square(100)
10000

위 코드는 enable_tracing 변수를 통해 log tracing 기능을 on/off 할 수 있다.

Leave a Reply

Your email address will not be published. Required fields are marked *