파이썬(python) - 데코레이터(Decorator)
데코레이터(Decorator)란?
데코레이터는 어떤 함수를 인자로 받아 꾸며준 후 다시 함수로 리턴하는 함수입니다.
여기서 꾸며준다는 의미는 함수의 앞, 뒤로 특정 로직을 추가하거나 하는 행동을 의미합니다.
함수 내부에 변화를 주지 않고 로직을 추가하고 싶을 때 사용합니다.
단, 데코레이터는 꾸며주는 함수의 내부에 직접적인 수정이나 로직 변환을 가할 순 없습니다.
* 데코레이터를 이해하기 위해선 일급 객체(First class citizen)와 클로저(Closure)에 대한 이해가 필요합니다.
해당 개념에 대해 잘 모르시는 분들은 이전 게시물을 참조해주세요.
데코레이터 살펴보기
데코레이터 자체는 이전에 살펴보았던 클로저와 비슷한 모양을 하고 있습니다.
def function1(func): # 인자로 데코레이터를 적용할 함수 전달
def function2(*args, **kwargs):
# 로직 추가
return func(*args, **kwargs) # 함수를 실행한 결과를 리턴함에 유의
return function2
다만 데코레이터는 함수를 인자로 전달한다는 특징이 있습니다.
아직 감이 잘 안잡히실텐데 예제와 함께 살펴봅시다.
def say_hello():
print("Hello")
여기 단순히 Hello 라고 프린트해주는 함수가 있습니다.
이 녀석을 데코레이터로 꾸며서 문장을 좀 더 풍부하게 구사하도록 만들어줍시다.
def decorator(func):
def sentence(*args, **kwargs):
print("Nice to meet you")
return func(*args, **kwargs)
return sentence
decorator라는 데코레이터를 만들었습니다.
이 녀석은 꾸며줄 함수의 앞에서 print로 "Nice to meet you"를 출력하는 녀석입니다.
아까 만든 say_hello 함수를 이 데코레이터로 꾸며봅시다.
@decorator
def say_hello():
print("Hello")
say_hello()
# 실행 결과
Nice to meet you
Hello
어떤 함수를 데코레이터로 꾸며주려면 그 함수의 선언부 위에 @데코레이터명을 적어주면 됩니다.
위에서 만든 decorator 함수로 say_hello함수를 꾸며줬고 실행했더니 decorator함수에서 추가된 기능인 "Nice to meet you" 문장이 함수 실행 전에 출력되는 것을 볼 수 있습니다.
아까 위에서 "함수를 인자로 전달"한다고 했는데 이는 데코레이터를 아래와 같은 방식으로도 적용할 수 있기 때문입니다.
def say_hello():
print("Hello")
hello = decorator(say_hello)
hello()
실행 결과는 위와 같습니다.
데코레이터는 상기한 예제처럼 함수의 실행 전 뿐만 아니라 실행 후 부분도 꾸밀 수 있습니다.
def decorator(func):
def sentence(name):
print("Nice to meet you")
func()
print(f"My name is {name}")
return
return sentence
@decorator
def say_hello():
print("Hello")
say_hello("Fox")
#실행 결과
Nice to meet you
Hello
My name is Fox
Hello 문장 뒤에 추가로 My name is... 문장이 추가된 것을 볼 수 있습니다.
또한 데코레이터는 중첩이 가능합니다.(중첩 데코레이터에 대해서는 추후 따로 다루겠습니다.)
데코레이터 파헤치기
이번엔 좀 더 자세히 데코레이터가 어떤 순서로 동작하는지 알아봅시다.
def decorator(func): #1
def sentence(*args, **kwargs): #3
print("Nice to meet you") #6
return func(*args, **kwargs) #7
return sentence #4
@decorator #3
def say_hello(): #2
print("Hello") #8
say_hello() #5
# 1. decorator 함수를 선언합니다.
# 2. say_hello 함수를 선언합니다.
# 3. say_hello 함수를 decorator 함수로 꾸며줍니다. 이는 variable = decorator(say_hello)와 동일합니다.
따라서 sentence 함수도 이 때 선언됩니다.
# 4. decorator 함수로 꾸며줬기 때문에 이제 say_hello 함수는 리턴된 sentence 함수의 로직을 가집니다.
# 5. say_hello 함수를 실행합니다.
# 6. 상기했듯 say_hello 함수는 sentence 함수의 로직을 가지고 있기 때문에 sentence 함수의 로직을 실행합니다.
# 7. 데코레이터로 꾸며준 함수, 즉 say_hello 함수를 호출하며 리턴합니다.
# 8. 마지막으로 호출된 say_hello 함수가 실행됩니다.
클래스 형식 데코레이터
데코레이터는 클래스로도 사용 가능합니다.
class Decorator:
def __init__(self, func): # 꾸며줄 함수를 매개변수로 전달
self.func = func # 꾸며줄 함수를 속성에 저장
def __call__(self, *args, **kwargs):
print("Nice to meet you")
self.func() #속성에 저장된 함수 호출
print(f"My name is {args[0]}")
@Decorator
def say_hello():
print("Hello")
say_hello("Fox")
#실행 결과
Nice to meet you
Hello
My name is Fox
__init__메서드는 인스턴스가 생성될때 실행되는 메서드이고
__call__메서드는 인스턴스를 함수처럼 호출할 때 실행되는 메서드입니다.
쉽게 생각하면 __call__메서드가 위 함수형 데코레이터의 역할을 한다고 보시면 됩니다.
데코레이터의 장, 단점
그렇다면 데코레이터를 써서 얻는 이점은 무엇이 있을까요?
데코레이터를 써서 얻는 최대 이점은 코드의 중복을 최소화 하고 재사용성을 높일 수 있다는 점입니다.
say_hello 함수를 다시 봅시다.
def say_hello():
print("Hello")
아까전에는 데코레이터를 이용해서 Hello 앞 뒤로 문장을 추가했었습니다.
그런데 사실 그렇게 귀찮게 할 것 없이 say_hello 함수를 직접 수정하는게 더 낫지 않을까요?
def say_hello(name):
print("Nice to meet you")
print("Hello")
print(f"My name is {name}")
물론 이것도 가능한 방법입니다.
하지만 프로젝트의 규모가 꽤 크다면 어떨까요?
저런 say_hello와 비슷한 역할을 하는 함수가 100개 있고, 그 함수마다 앞 뒤로 같은 로직을 추가하고 싶다면?
100개의 함수를 다 찾아서 하나하나 로직을 수정하기엔 너무 힘들고 중복이 많아지겠죠..
이럴 때 데코레이터를 사용하면 각 함수 위에 @데코레이터명 하나만 붙이면 되니 중복을 최소화하고 가독성도 높일 수 있습니다. 또한 로직에 수정이 필요할 때도 데코레이터만 수정하면 되니 훨신 수정이 용이하죠.
하지만 이런 데코레이터도 단점은 존재합니다.
데코레이터를 하나만 사용한다면 모르겠지만 중첩해서 사용한다면 에러 발생시에 디버깅 난이도가 꽤 상승합니다.
에러가 발생한 지점을 추적하기 어렵기 때문이죠.
또한 코드에 대한 이해도가 없다면 오히려 가독성이 떨어집니다. 해당 데코레이터가 무슨 로직을 추가해주는지 잘 이해하고 있다면 상관없지만 잘 모른다면 데코레이터 로직까지 다 찾아서 이해를 시도해야 합니다.
그리고 과도한 남용은 오히려 재사용성을 떨어뜨리니 적절히 사용해야 합니다. 어쩔때는 그냥 내부 로직으로 추가하는게 더 나을수도 있다는 사실..