python으로 작성된 코드들을 보다 보면 @로 시작하는 구문들을 볼 수 있습니다.
@decorator_
def function():
print("what is decorator?")
Decorator를 한마디로 얘기하자면, 대상 함수를 Wrapping 하고, Wrapping 된 함수의 앞뒤에 추가적으로 꾸며질 구문들을 정의해서 손쉽게 재사용 가능하게 해주는 것입니다.
Decorator는 감싸고 있는 함수를 호출하기 전이나 후에 추가로 코드를 실행하는 기능을 갖췄습니다. 이 기능으로 입력 인수와 반환 값을 접근하거나 수정할 수 있습니다. 이 기능은 시맨틱 강조, 디버깅, 함수 등록을 비롯해 여러 상황에 유용합니다.
Decorator는 어떤 경우에 쓰이는가?
메인 구문이 있고, 여기에 부가적인 구문을 추가하고 싶을 때. 그리고 부가적인 구문을 반복해서 사용하고 싶은 경우도 있습니다. 이 때 부가적인(그리고 반복적인) 작업을 Decorator로 선언해서 자유롭게 사용이 가능하다는 것입니다.
예를 들어 아래와 같은 함수가 있다고 생각해보면 문장을 출력하는 매우 간단한 함수입니다.
def main_function():
print("MAIN FUNCTION START")
이 함수에 예를들어 해당 문장을 출력하기 전과 후에 날짜와 시간을 출력하고 싶다면 아래와 같이 할 수 있습니다.
import datetime
def main_function():
print(datetime.datetime.now())
print("MAIN FUNCTION START")
print(datetime.datetime.now())
이 예제만 놓고 보면, 간단한 함수이고 특별히 복잡하게 추가할게 없는 작업이기 때문에, 이렇게 해도 큰 무리는 없습니다.
그런데 만약 이와 같은 패턴의 함수가 여러번 있다면 어떻게 될까요?
def main_function_1():
print("MAIN FUNCTION 1 START")
def main_function_2():
print("MAIN FUNCTION 2 START")
def main_function_3():
print("MAIN FUNCTION 3 START")
#.....
반복되는 구문이 많아지면 점점 소스가 길어지며, 가독성도 떨어집니다. 지금은 예문은 메인과 추가 구문이 한 줄 밖에 안되기 때문에 매우 간단하지만, 실제 코딩할 때의 상황을 생각해보면 문제는 커집니다.
Decorator 사용하기
아래는 위의 코드에 Decorator를 적용한 것입니다.
import datetime
def datetime_decorator(func):
def decorated():
print(datetime.datetime.now())
func()
print(datetime.datetime.now())
return decorated
@datetime_decorator
def main_function_1():
print("MAIN FUNCTION 1 START")
main_function_1()
Decorator 함수를 재사용 함으로써, main 함수에 대한 가독성과 직관성이 훨씬 좋아진 것을 볼 수 있습니다. 그리고 같은 패턴을 여러번 사용하더라도 간단히 @를 붙이면 끝이므로 사용도 간편합니다.
Decorator 선언된 부분을 보면
- 먼저 Decorator 역할을 하는 함수를 정의하고, 이 함수에서 decorator가 적용될 함수를 인자로 받습니다. Python은 함수를 인자로 다른 함수를 받을 수 있다는 특징을 이용하는 것입니다.
- Decorator 역할을 하는 함수 내부에 또 한번 함수를 선언(nested function)하여 여기에 추가적인 작업(시간 출력)을 선언해 주는 것입니다.
- nested 함수를 retur 해주면 됩니다.
마지막으로, main 함수들의 앞에 @를 붙여 Decorator 역할을 하는 함수를 호출해 줍니다.
추가적으로 Decorator가 꾸며주는 기능이라고 해서 대상 함수의 수행 중간에 끼어드는 구문은 할 수 없습니다. Decorator는 원래 작업의 앞 뒤에 추가적인 작업을 손쉽게 사용 가능하도록 도와주는 역할입니다.
Decorator는 Class 형태로 사용
Decorator를 Class로 사용하고 싶다면 __call__ 함수로 Decorator 형식을 정의해 주면 됩니다.
Class의 __call__ 함수로 정의해주는게 nested 함수 형식으로 정의한 것 보다 더 깔끔해보입니다.
import datetime
class DatetimeDecorator:
def __init__(self, f):
self.func = f
def __call__(self, *args, **kwargs):
print datetime.datetime.now()
self.func(*args, **kwargs)
print datetime.datetime.now()
class MainClass:
@DatetimeDecorator
def main_function_1():
print "MAIN FUNCTION 1 START"
@DatetimeDecorator
def main_function_2():
print "MAIN FUNCTION 2 START"
@DatetimeDecorator
def main_function_3():
print "MAIN FUNCTION 3 START"
my = MainClass()
my.main_function_1()
my.main_function_2()
my.main_function_3()
참고)