Decorate 개념

decorator : 실내장식가

데커레이터(Decorator)는 하나의 함수를 취해서 또 다른 함수를 반환하는 함수입니다.

@decorator
def function():
    print "what is decorator?"

한마디로 얘기하자면, 대상 함수를 wrapping 하고, 이 wrapping 된 함수의 앞뒤에 추가적으로 꾸며질 구문 들을 정의해서 손쉽게 재사용 가능하게 해주는 것입니다....?(자세한 설명은 밑에서)

 

 

 

 

 

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()

 

위와 같이 작성할 수 있습니다.

하지만 위와 같은 작업을 여러번 반복해야 한다면?

import datetime

def main_function_1():
     print datetime.datetime.now()
     print "MAIN FUNCTION 1 START"
     print datetime.datetime.now()

def main_function_2():
     print datetime.datetime.now()
     print "MAIN FUNCTION 2 START"
     print datetime.datetime.now()

def main_function_3():
     print datetime.datetime.now()
     print "MAIN FUNCTION 3 START"
     print datetime.datetime.now()

.... X 100번

 

 

반복되는 구문이 많다보니 소스가 지저분해져 가독성이 떨어집니다.

함수 실행 전과 후에 시간을 출력하는 간단한 구문이 이런데 실제 사용될 때는 소스의 상태가 심각해질 것입니다.

이럴때 아래와 같이 데코레이터를 사용할 수 있다.

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"

@datetime_decorator
def main_function_2():
        print "MAIN FUNCTION 2 START"

@datetime_decorator
def main_function_3():
        print "MAIN FUNCTION 3 START"

..... X 100번

 

 

decorator 함수를 재사용 함으로써, main 함수에 대한 가독성과 직관성이 훨씬 좋아진 것을 볼 수 있습니다다.

그리고 같은 패턴을 여러번 사용하더라고 간단히 @를 붙이면 끝이므로 사용도 간편합니다.

아래에서는 데코레이터의 사용 예시와 함께 설명해보겠습니다.

 

 

 

 

 

데코레이터 예제1 : 수동으로 활용하기

# 데코레이터 예제 

def decorator_function(original_function): #1, #4 
    def wrapper_function(): #5 #8 
        return original_function() #9 
    return wrapper_function #6 

def display(): #2, #10 
    print("display 함수가 실행됐습니다") #11 

decorated_display = decorator_function(display)  #3 display 함수를 전달 
decorated_display() #7 

출력결과 :

display 함수가 실행됐습니다

display 함수가 실행됐습니다
None

 

위 코드는 #의 순서를 따른다.

  1. #3: decorated_display라는 변수는 decorated_function 함수의 리턴값을 할당받는다.
  2. #6: return값은 wrapper_function이라는 함수다.
  3. #3변수는 결국 함수를 실행시킬 수 있는 함수다.
  4. #7: 변수가 함수니까 괄호를 붙여 실행시켜보자.
  5. #8, #9 : return 값으로 original_function을 돌려준다.
    • 여기서 closure 함수의 개념이 쓰인다.
    • 바깥 함수(decorator_function)에서 전달받은 original_function을 기억하고 있다.
  6. #10, #11 : 여기서 비로소 함수를 실행하고, print문을 실행한다.

display함수에서 반환값이 없기 때문에 함수 자체의 반환값은 None입니다!

 

 

 

# 데코레이터 예제 

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 함수가 실행됐습니다.")

def display_2():
    print("display_2 함수가 실행됐습니다.")

display_1 = decorator_function(display_1)
display_2 = decorator_function(display_2)

display_1()
print("")
display_2()

wrapper 함수를 통해서 간단하게 기능 추가를 할 수 있습니다.

 

 

 

 

데코레이터 예제2 : @ 심볼 활용하기

# @ 심볼 활용하기 

def decorator_function(original_function):
    def wrapper_function():
        print("{} 함수가 호출되기 전입니다.".format(original_function.__name__))
        return original_function()
    return wrapper_function

@decorator_function
def display_1():
    print("display_1 함수가 실행됐습니다.")

@decorator_function
def display_2():
    print("display_2 함수가 실행됐습니다.")

# display_1 = decorator_function(display_1) 
# display_2 = decorator_function(display_2) 

출력 결과:

display_1() # 변수에 함수형 리턴값 할당 없이 바로 함수 호출이 가능하다. 
display_2() # 변수에 함수형 리턴값 할당 없이 바로 함수 호출이 가능하다. 

 

 

 

 

 

 

데코레이터 예제3 : 인자가 전달되는 함수는 어떻게 데코레이팅 할까?

예제3.1 : 위치인자와 키워드인자 활용하기

def decorator_function(original_function):
    def wrapper_function():
        print("{} 함수가 호출되기 전입니다.".format(original_function.__name__))
        return original_function()
    return wrapper_function

@decorator_function
def display_1():
    print("display_1 함수가 실행됐습니다.")

@decorator_function
def display_info(name, age):  # 위 예제와 다르게 인자가 전달된다.
    print("display_info( {}, {} ) 함수가 실행됐습니다.".format(name, age))

# display_1 = decorator_function(display_1) 
# display_2 = decorator_function(display_2) 

display_1()
display_info('김아무개', 37)

출력 결과:

 TypeError: wrapper_function() takes 0 positional arguments but 2 were given

 

display_1 함수는 정상적으로 출력되지만, display_info 함수는 타입에러가 생겼습니다.

wrapper 함수에 위치인자와 키워드인자를 넣어주면 해결됩니다.

 

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print("{} 함수가 호출되기 전입니다.".format(original_function.__name__))
        return original_function(*args, **kwargs)
    return wrapper_function

 

 

 

예제3.2 : 위치인자와 키워드인자 활용하기

다음 예제는 Introducing Python이라는 책에서 발췌한 예제이다.

여기서는 위치인자와 키워드인자까지 활용했습니다.

def document_it(func):
    def new_function(*args, **kwargs):
        print('Running function : ', func.__name__)
        print('Positional arguments : ', args)
        print('Keyword arugments :' , kwargs)
        result = func(*args, **kwargs)
        print('Result : ', result)
        return result
    return new_function

def add_inits(a, b):
    return a + b


def div_inits(a,b):
    return a / b

add = document_it(add_inits)
div = document_it(div_inits)

print(add(5,3))
print("")
print(div(5,3))


출력 결과:

Running function :  add_inits
Positional arguments :  (5, 3)
Keyword arugments : {}
Result :  8
8

Running function :  div_inits
Positional arguments :  (5, 3)
Keyword arugments : {}
Result :  1.6666666666666667
1.6666666666666667

 

 

 

 

 

데코레이터 예제4 : 인자가 전달되는 함수는 어떻게 데코레이팅 할까?

데코레이터에서 파라미터를 입력받고 싶다면, 조금 복잡한데 함수를 2개로 만들어서 이용합니다.

반환값인 return을 잘 맞추어서 사용해야 합니다.

from functools import wraps
        def print_a(file_name):
                def decorate(func):
                @wraps(func)
                def wrapper(*args, **kwargs):
                        print(file_name)
                        return func(*args, **kwargs)
                return wrapper
        return decorate

@print_a("hahaha")  #데코레이터에 "hahaha"를 입력받았다!
def print_c():
        print("c")

print_c()

결과:

hahaha
c

 

 

 

 

 

데코레이터 예제5 : class 형태로 decorator를 사용해 보기.

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())
                print('\n')

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()

 

 

 

 

 

그래서, 데코레이터를 언제 쓰는데?

  1. 로그를 남길 때
  2. 유저의 로그인 상태를 확인하여 로그인 페이지로 리다이렉트(redirect)
  3. 프로그램 성능을 위한 테스트

이렇게 파이썬 데코레이터란 무엇이고, 어떻게 사용해야 하는지에 대해서 알아보았습니다.

안에 wrapper함수 뒤에 코드를 정의하면, 함수 실행을 마치고 실행되야 하는 코드도 정의할 수 있습니다.

데코레이터의 활용범위는 생각보다 넓을 듯 합니다.

또한 함수별로 필수적으로 실행해야 하는 기능을 한 번에 정의할 수 있어 편리하고, 특히 유지보수 하기에 용이합니다.

 

 

 

그리고 이건 파이썬 데코레이터를 작성하는 방법을 배워야하는 이유 5가지라는 제목의 글이다.

www.hanbit.co.kr/media/channel/view.html?cms_code=CMS5689111564

 

 

 

 

참조:

https://whatisthenext.tistory.com/113

https://tariat.tistory.com/846

https://bluese05.tistory.com/30

http://schoolofweb.net/blog/posts/파이썬-데코레이터-decorator/

 

반응형

+ Recent posts