파이썬

파이썬(python) - 데코레이터(Decorator)

티베트 모래여우 2020. 10. 9. 20:20
반응형

데코레이터(Decorator)란?

데코레이터는 어떤 함수를 인자로 받아 꾸며준 후 다시 함수로 리턴하는 함수입니다.

여기서 꾸며준다는 의미는 함수의 앞, 뒤로 특정 로직을 추가하거나 하는 행동을 의미합니다.

함수 내부에 변화를 주지 않고 로직을 추가하고 싶을 때 사용합니다.

단, 데코레이터는 꾸며주는 함수의 내부에 직접적인 수정이나 로직 변환을 가할 순 없습니다.

함수를 이쁘게 색칠해 주는 역할이라도 하는 걸까요?

 

* 데코레이터를 이해하기 위해선 일급 객체(First class citizen)와 클로저(Closure)에 대한 이해가 필요합니다.

해당 개념에 대해 잘 모르시는 분들은 이전 게시물을 참조해주세요.

 

파이썬(python) - 일급 객체(first-class citizen)

일급 객체(First-class citizen)란? 일급 객체는 OOP에서 사용되는 개념 중 하나로 아래의 조건을 만족하는 객체를 의미합니다. 1. 변수 혹은 데이터 구조(자료구조) 안에 담을 수 있어야 한다. 2. 매개변

tibetsandfox.tistory.com

 

 

파이썬(python) - 클로저(Closure)

*글을 들어가기 앞서 클로저를 이해하기 위해선 일급 객체(first-class citizen)에 대한 이해가 필요합니다. 일급 객체에 대해 잘 모르시는 분은 아래 링크를 참조해주세요. (tibetsandfox.tistory.com/8) 파이

tibetsandfox.tistory.com


데코레이터 살펴보기

데코레이터 자체는 이전에 살펴보았던 클로저와 비슷한 모양을 하고 있습니다.

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개의 함수를 다 찾아서 하나하나 로직을 수정하기엔 너무 힘들고 중복이 많아지겠죠..

죽..여줘..

이럴 때 데코레이터를 사용하면 각 함수 위에 @데코레이터명 하나만 붙이면 되니 중복을 최소화하고 가독성도 높일 수 있습니다. 또한 로직에 수정이 필요할 때도 데코레이터만 수정하면 되니 훨신 수정이 용이하죠.

하지만 이런 데코레이터도 단점은 존재합니다.

데코레이터를 하나만 사용한다면 모르겠지만 중첩해서 사용한다면 에러 발생시에 디버깅 난이도가 꽤 상승합니다.

에러가 발생한 지점을 추적하기 어렵기 때문이죠.

또한 코드에 대한 이해도가 없다면 오히려 가독성이 떨어집니다. 해당 데코레이터가 무슨 로직을 추가해주는지 잘 이해하고 있다면 상관없지만 잘 모른다면 데코레이터 로직까지 다 찾아서 이해를 시도해야 합니다.

그리고 과도한 남용은 오히려 재사용성을 떨어뜨리니 적절히 사용해야 합니다. 어쩔때는 그냥 내부 로직으로 추가하는게 더 나을수도 있다는 사실..

뭐든지 적당한게 최고

반응형