[Programming Language]/[Python]

[Python] :: 데코레이터(decorator) - '@'

Semincolon 2023. 7. 10. 15:51

◎ 데코레이터(decorator)

  데코레이터란 이름에서도 알 수 있듯이 꾸며주는 것을 말한다. 여기서 꾸며주는 것의 대상은 함수클래스로 구분할 수 있다.

 

1) 함수를 꾸며주는 데코레이터

  먼저 함수를 꾸며주는 데코레이터부터 살펴보도록 하자. 데코레이터는 '@' 기호를 통해 만들어진다. 아래는 간단한 함수 데코레이터의 예제다.

def test(function):     # 호출한 함수 'print_hello()'가 매개변수로 들어감
    def wrapper():
        print(function.__name__, "Start")
        function()
        print(function.__name__, "End")
    return wrapper

# 데코레이터 생성
@test
def print_hello():
    print("Hello !!!")

# 'print_hello()' 함수 호출
print_hello()

  test 함수는 매개변수 function을 가진다. test 함수 내부에는 매개변수 funtion을 감싸는 함수 wrapper()이 존재한다. wrapper() 함수는 "Start"를 출력하고 나서 매개변수로 받은 function() 함수를 호출한 뒤 "End"를 출력한다. 그리고 정의된 print_hello() 함수는 단순 "Hello !!!"를 출력하는 기능이지만 데코레이터 기능을 수행할 함수로 test를 지정하였다. 이로써 print_hello() 함수가 호출될 때 데코레이터인 test() 함수가 호출되며 이때 test() 함수의 매개변수로 호출 함수인 print_hello() 함수가 들어가게 되는 것이다.

 

1-1) 데코레이터는 어떨 때 사용하는 것일까?

  데코레이터는 보통 여러 함수에 대해 동일한 내용을 실행할 경우에 사용된다. 예를 들어 아래와 같은 코드가 있다고 가정하자.

def fun1():
    print(fun1.__name__)

def fun2():
    print(fun2.__name__)

def fun3():
    print(fun3.__name__)

def fun4():
    print(fun4.__name__)

# 호출
fun1()
fun2()
fun3()
fun4()

  여기서 각 함수가 호출될 때 "(함수명) 실행"이 출력되고 종료될 때는 "(함수명) 종료"가 출력되게끔 하려면 다음과 같이 코드를 추가하면 된다.

def fun1():
    print(fun1.__name__, "실행")
    print(fun1.__name__)
    print(fun1.__name__, "종료")

def fun2():
    print(fun2.__name__, "실행")
    print(fun2.__name__)
    print(fun2.__name__, "종료")

def fun3():
    print(fun3.__name__, "실행")
    print(fun3.__name__)
    print(fun3.__name__, "종료")

def fun4():
    print(fun4.__name__, "실행")
    print(fun4.__name__)
    print(fun4.__name__, "종료")

# 호출
fun1()
fun2()
fun3()
fun4()

  그러나 만약 함수의 수가 100개인 경우라면 위 방식대로 동일한 코드를 100번 작성해야 하므로 매우 비효율적인 코드가 될 것이다. 이럴 때 사용하는 것이 바로 데코레이터이다. 위 코드에 데코레이터를 적용한 모습을 확인해보자.

def decorator(func):
    def wrapper():
        print(func.__name__, "실행")
        func()
        print(func.__name__, "종료")
    return wrapper

@decorator
def fun1():
    print(fun1.__name__)

@decorator
def fun2():
    print(fun2.__name__)

@decorator
def fun3():
    print(fun3.__name__)

@decorator
def fun4():
    print(fun4.__name__)

# 호출
fun1()
fun2()
fun3()
fun4()

  데코레이터로 사용할 함수 decorator()를 정의하고 각 함수의 선언에 데코레이터를 설정해주었다. 출력 결과가 위와 다른 부분이 존재하는데 4개의 각 함수의 print() 부분이 실행될 때 각 함수의 이름이 아니라 "wrapper"이 출력된다는 것이다. 이는 데코레이터를 생성함으로써 함수의 실질적인 호출이 이뤄지는 곳은 decorator() 함수 내부의 wrapper() 함수이기 때문이다. 이렇듯 데코레이터를 사용하면 동일한 코드를 중복으로 사용하지 않아도 데코레이터를 설정해주기만 하면 되므로 코드가 훨씬 간결해지게 된다.

 

2) 클래스를 꾸며주는 데코레이터

  데코레이터는 클래스에도 적용할 수 있다. 클래스를 데코레이터로 만들기 위해서는 먼저 호출할 함수를 __init__ 함수를 통해 전달받는다. 이후 전달받은 함수를 속성으로 저장하고 __call__ 함수에서 이를 활용하면 된다. 데코레이터를 사용하는 것은 함수의 경우와 동일하다.

class Decorator:
    def __init__(self, f):    # 호출할 함수를 매개변수 f로 받음
        self.func = f         # 매개변수 f를 func에 저장
 
    def __call__(self):
        print(self.func.__name__, '함수 시작')
        self.func()           # 저장했던 func 사용하여 호출
        print(self.func.__name__, '함수 끝')

@Decorator 
def hello():
    print('hello')
 
# 호출
hello()