Decorator
함수를 장식하는 기능, 데코레이터는 함수를 수정하지 않은 상태에서 추가 기능을 구현할 때 사용한다.
클래스에서 메서드를 만들 때 @staticmethod, @classmethod, @abstractmethod 등을 붙였는데, 이렇게 @ 로 시작하는 것들이 데코레이터이다.
Closure
데코레이터는 closure 함수의 개념을 활용한다. 다음과 같은 클로저 함수의 동작을 보자.
def decorator_function(original_function):
def wrapper_function():
print ('{} 함수가 호출되기전 입니다.'.format(
original_function.__name__))
return original_function()
return wrapper_function
def display_1():
print ('display_1 함수가 실행됐습니다.')
display_1 = decorator_function(display_1) #1
display_1()display_1 함수가 호출되기전 입니다.
display_1 함수가 실행됐습니다.
위 코드에서 가장 먼저 호출되는 것은 decorator_function 이다. decorator_function 은 decorator_function(display_1) 에 의해 호출되어 wrapper_function 을 반환한다.
그리고 display_1() 은 wrapper_function 을 호출하게 되는데, 이때 wrapper_function 안에서 original_function 인 display_1 함수가 호출된 결과값이 반환된다.
Using Decorator
위와 같은 동작은 decorator 를 이용하면 좀 더 간결하게 표현할 수 있다.
@decorator_function #1
def display_1():
print ('display_1 함수가 실행됐습니다.')
display_1() # same as decorator_function(display_1)()display_1 함수가 호출되기전 입니다.
display_1 함수가 실행됐습니다.
인수를 가진 함수에 decorator 를 붙이려면 다음과 같이 decorator_function 을 수정한다.
def decorator_function(original_function):
def wrapper_function(*args, **kwargs): #1
print ('{} 함수가 호출되기전 입니다.'.format(
original_function.__name__))
return original_function(*args, **kwargs) #2
return wrapper_function
@decorator_function
def display():
print ('display 함수가 실행됐습니다.')
@decorator_function
def display_info(name, age):
print ('display_info({}, {}) 함수가 실행됐습니다.'.format(name, age))
display()
print()
display_info('John', 25)display 함수가 호출되기전 입니다.
display 함수가 실행됐습니다.
display_info 함수가 호출되기전 입니다.
display_info(John, 25) 함수가 실행됐습니다.
- 데코레이터 함수에
*args와**kwargs인자를 추가한 것을 확인할 수 있다.
굳이 가변 인자를 추가하지 않아도 된다. 아래 데코레이터 함수는 고정된 개수의 인자들을 입력으로 받는다.
def trace(func): # 호출할 함수를 매개변수로 받음
def wrapper(a, b): # 호출할 함수 add(a, b)의 매개변수와 '똑같이' 지정
r = func(a, b) # func에 매개변수 a, b를 넣어서 호출하고 반환값을 변수에 저장
print('{0}(a={1}, b={2}) -> {3}'.format(func.__name__, a, b, r)) # 매개변수와 반환값 출력
return r # func의 반환값을 반환
return wrapper # wrapper 함수 반환
@trace # @데코레이터
def add(a, b): # 매개변수는 두 개
return a + b # 매개변수 두 개를 더해서 반환
print(add(10, 20))add(a=10, b=20) -> 30
30
일반적으로 데코레이터는 프로그램의 로깅 (logging) 기능이나 성능을 테스트하기 위해서 많이 사용된다.
def my_timer(original_function): #1
import time
def wrapper(*args, **kwargs):
t1 = time.time()
result = original_function(*args, **kwargs)
t2 = time.time() - t1
print '{} 함수가 실행된 총 시간: {} 초'.format(original_function.__name__, t2)
return result
return wrapper
@my_timer #2
def display_info(name, age):
time.sleep(1)
print 'display_info({}, {}) 함수가 실행됐습니다.'.format(name, age)
display_info('John', 25)display_info(John, 25) 함수가 실행됐습니다.
display_info 함수가 실행된 총 시간: 1.00157594681 초
두 개의 Decorator
하나의 함수에 두 개의 decorator 함수를 중첩하여 사용할 수 있다.
import time
def my_func(original_function):
print('my_func 함수 호출')
print('인자 함수 {}'.format(original_function.__name__))
def my_func_wrapper(a, b):
print('my_func_wrapper 함수 호출')
print('인자 함수 {}'.format(original_function.__name__))
result2 = original_function(a, b)
return result2
return my_func_wrapper
def my_timer(original_function):
start_time = time.time()
print('my_timer 함수 호출')
print('인자 함수 {}'.format(original_function.__name__))
def my_timer_wrapper(name, age):
print('my_timer_wrapper 함수 호출')
print('인자 함수 {}'.format(original_function.__name__))
result1 = original_function(name, age)
end_time = time.time() - start_time
print('실행 시간 {}'.format(end_time))
return result1
return my_timer_wrapper
@my_func
@my_timer
def display_info(name, age):
time.sleep(1)
print('{} -> {}'.format(name, age))
display_info('James', 30)my_timer 함수 호출
인자 함수 display_info
my_func 함수 호출
인자 함수 my_timer_wrapper
my_func_wrapper 함수 호출
인자 함수 my_timer_wrapper
my_timer_wrapper 함수 호출
인자 함수 display_info
James -> 30
실행 시간 1.0018019676208496
복수의 데코레이터를 쌓아가면서 사용하면, 가장 아래쪽의 데코레이터 함수부터 실행이된다. 즉, my_timer 함수가 display_info 를 인자로 받으면서 먼저 실행되고, 이 함수가 반환하는 값 (my_timer_wrapper) 가 my_func 의 인자로 들어가고 실행된다.
my_func 에서는 my_func_wrapper 를 반환하고, 최종적으로 display_info('James', 30) 은 반환된 my_func_wrapper 를 호출한다.
my_func_wrapper는my_func이 인자로 받은my_timer_wrapper를 호출하고, 다시my_timer_wrapper는 인자로 받은display_info를 호출하게 된다.- 그리고
display_info가 반환한 값은 아무것도 없으므로,result1은None값이 되고, 이result1값은result2값으로 들어가게 된다. 결과적으로display_info('James',30)는result2를 반환하게 된다.
정리하자면 호출 순서는 다음과 같다. 괄호는 중첩된 데코레이터로 인해 display_info('James', 30) 수행 전 호출된 함수의 순서다.
(my_timer -> my_func ->) `my_func_wrapper` -> `my_timer_wrapper` -> `display_info` --- (반환) --> `my_timer_wrapper` -> `my_func_wrapper` -> 최종 출력Decorator 종류
Final
@typing.final 데코레이터는 함수를 non-overridable 하게 만든다. 즉, subclass 들은 동일한 함수에 대한 다른 구현을 수행하지 못한다.
만약 함수가 아니라 클래스에 final 데코레이터를 씌웠다면, 해당 클래스는 상속받지 못하는 클래스가 된다 (subclass 생성 불가).
참고: stackoverflow