기록과 정리의 공간

[Python] 데코레이터(decorator) 본문

언어/Python

[Python] 데코레이터(decorator)

딸기맛도나쓰 2020. 9. 4. 18:11

1. 데코레이터 (decorator)

  • @데코레이터이름과 같은 형태로 사용하며, 함수(메서드)를 장식한다.
  • 데코레이터는 기존에 작성된 함수에 추가 기능을 구현하고자 할 때, 해당 함수를 수정하지 않고도 추가 기능을 사용할 수 있도록 해준다.
  • 데코레이터 하나로 여러 함수에 동일한 기능을 손쉽게 추가할 수 있다.
  • 예를 들어 파라미터가 있는 함수에 파라미터의 유효성 검사가 필요할 때, 파라미터를 가지고 있는 함수마다 유효성 검사 코드를 일일이 넣기에는 매우 비효율적이다. 또한, 일일이 넣었다고 할지라도 유효성 검사와 관련된 코드를 수정해야할 때 함수 마다 일일이 해당 코드를 수정해야하는 불편함이 있다. 이런 경우 데코레이터를 사용하면 매우 편리하다.
  • 자세한 것은 예시 코드를 살펴보자.
    • 예시1 - 데코레이터 사용 예시
import datetime

# 데코레이터 작성하기
def datetime_decorator(func):           # 데코레이터로 사용될 이름으로 함수를 작성한다.
    def wrapper():                      
        print (str(datetime.datetime.now())) # 데코레이터를 사용할 함수 앞에서 실행할 내용
        func()                          # 데코레이터를 사용할 함수  
        print (datetime.datetime.now()) # 데코레이터를 사용할 함수 뒤에서 실행할 내용
    return wrapper                      # 클로저 함수로 만든다.

@datetime_decorator # 데코레이터 사용
def logger_login_rayon():
    print("Ryan login")

@datetime_decorator # 데코레이터 사용
def logger_login_apeach():
    print("Apeach login")

logger_login_rayon()
print("--------------------------")
logger_login_apeach()


"""
결과값:
2020-09-04 16:13:27.531143
Ryan login
2020-09-04 16:13:27.531143
--------------------------
2020-09-04 16:13:27.531143
Apeach login
2020-09-04 16:13:27.531143
"""
  • 위 코드와 같이 데코레이터를 사용하면, 기존 함수를 수정하지 않고도 새로운 기능을 추가할 수 있으며, 여러 기능을 쉽게 추가할 수 있다. 반면에 데코레이터를 사용하지 않는다면 아래와 같이 함수 안에 일일이 새로운 코드를 추가해줘야하는데, 아래 코드는 길이가 매우 짧으므로 데코레이터 없이 작성해도 불편함이 없지만, 만약에 새로운 기능에 해당하는 코드가 매우 길다면 함수마다 일일이 새로운 코드를 작성하기 위해서는 많은 시간이 필요할 것이다. 추후에 수정이 필요할 때도 함수마다 일일이 수정해줘야하므로 굉장히 비효율적일것이다.
def logger_login_muzi():
    print (str(datetime.datetime.now()))
    print("Muzi login")
    print (datetime.datetime.now())
  • 예시2 - 예시1의 첫번 째 코드를 데코레이터를 사용하지 않고, 중첩 함수와 클로저 함수만 이용하여 같은 결과값을 출력해보자.
def datetime_decorator(func):
    def wrapper():
        print(str(datetime.datetime.now()))
        func()
        print(datetime.datetime.now())
    return wrapper

#@datetime_decorator - 데코레이터 주석처리
def logger_login_rayon():
    print("Ryan login")

#@datetime_decorator - 데코레이터 주석처리
def logger_login_apeach():
    print("Apeach login")

decorated_func = datetime_decorator(logger_login_rayon)
decorated_func()
print("--------------------------")
decorated_func = datetime_decorator(logger_login_apeach)
decorated_func()


"""
결과값:
2020-09-04 16:13:27.531143
Ryan login
2020-09-04 16:13:27.531143
--------------------------
2020-09-04 16:13:27.531143
Apeach login
2020-09-04 16:13:27.531143
"""
  • 예시3 - 파라미터가 있는 함수에 데코레이터 적용하기
    • 내부함수(중첩함수)에 데코레이터를 사용하고자 하는 함수와 동일한 인자를 넘겨주면 된다.
    • 아래 예시는 유효성 검사의 한 예이다.
def outer_func(function):
    def inner_func(n1, n2):
        if n2 == 0:                     
            print('cannot be divided with zero')
            return
        function(n1, n2)
    return inner_func

@outer_func
def divide(n1, n2):
    print(n1 / n2)

divide(4, 2) # 결과값 : 2.0
divide(4, 0) # 결과값 : cannot be divided with zero
  • 예시4 - 파라미터와 관계없이 모든 함수에 적용 가능한 데코레이터 만들기
    • 파라미터는 어떤 형태이든 결국 (*args_, **_kwargs)로 표현이 가능하다.
    • 데코레이터의 내부함수(중첩함수)의 파라미터를 위와 같은 식으로 작성하면 어떤 함수이든 데코레이터 적용이 가능하다.
def general_decorator(func):
    def wrapper(*args, **kwargs):
        print('function is decorated')
        func(*args, **kwargs)
    return wrapper

def calc_plus2(n1, n2):
    print(n1 + n2)

def calc_plus3(n1, n2, n3):
    print(n1 + n2 + n3)

def calc_plus4(n1, n2, n3, n4):
    print(n1 + n2 + n3 + n4)

calc_plus2(1, 2) # 결과값 : 3
calc_plus3(1, 2, 3) # 결과값 : 6
calc_plus4(1, 2, 3, 4) # 결과값 : 10 
  • 예시5 - 한 함수에 데코레이터 여러 개 지정하기
    • 데코레이터를 나열한 순서대로 실행된다.
def deco1(func):
    def wrapper():
        print('decorator1 start')
        func()
        print('decorator1 end')
    return wrapper

def deco2(func):
    def wrapper():
        print('decorator2 start')
        func()
        print('decorator2 end')
    return wrapper

@deco1
@deco2
def print_10():
    print(10)

print_10() 


"""
결과값:
decorator1 start
decorator2 start
10
decorator2 end
decorator1 end
"""
  • 위의 코드를 데코레이터 없이 풀어서쓰면 아래와 같다.
    • #1과 결과값에서 알 수 있듯이, deco2(print_10)의 리턴값이 통째로 deco1의 인자인 func로 넘겨진다는 것을 알 수 있다.
def deco1(func):
    def wrapper():
        print('decorator1 start')
        func()
        print('decorator1 end')
    return wrapper

def deco2(func):
    def wrapper():
        print('decorator2 start')
        func()
        print('decorator2 end')
    return wrapper

# @deco1 - 데코레이터 주석처리
# @deco2 - 데코레이터 주석처리
def print_10():
    print(10)

decorated_func = deco1(deco2(print_10)) #1
decorated_func()

"""
결과값:
decorator1 start
decorator2 start
10
decorator2 end  
decorator1 end 
"""
  • 연습 문제1 - type_checker데코레이터 만들기(인자 유효성 검사). n1, n2를 곱한 값을 출력하는 함수를 만들고, type_checker데코레이터로 n1, n2가 정수가 아니면 'only integer support'를 출력하고 종료한다.
def type_checker(func):
    def wrapper(n1, n2):
        if type(n1) != int or type(n2) != int:
            print("only integer support")
            return
        func(n1, n2)
    return wrapper

@type_checker
def multiply(n1, n2):
    print(n1 * n2)

multiply(2.2, 4) # 결과값: only integer support
multiply(2, 4) # 결과값 : 8
  • 연습 문제2 - 아래 결과값이 출력되도록 코드 작성해보기.
# 결과값

<b><i>HELLO!</i></b>
# 정답 코드

def add_bold_tag(func):
    def wrapper(*args, **kwargs):
        return '<b>{}</b>'.format(func(*args, **kwargs))
    return wrapper

def add_italic_tag(func):
    def wrapper(*args, **kwargs):
        return "<i>{}</i>".format(func(*args, **kwargs))
    return wrapper

@add_bold_tag
@add_italic_tag
def add_html(text, text1):
    return text, text1

print(add_html("HELLO!"))

2. 파라미터가 있는 데코레이터 만들기(심화)

  • 아래 코드와 같이 가장 안쪽에 중첩 함수를 하나 더 두면 된다.
def deco1(num): # 데코레이터에 num이라는 파라미터를 전달
    def outer_wrapper(func): # 중첩 함수1
        def inner_wrapper(*args, **kwargs): # 중첩 함수2 
            print("deco1's param : {}".format(num))
            func(*args, **kwargs)
        return inner_wrapper
    return outer_wrapper

@deco1(1) #1
def print_hello():
    print('hello')

print_hello() #2

"""
결과값:
deco1's param : 1
hello
"""
  • 위 코드에서 #1부터 #2까지의 코드를 데코레이터가 없는 코드로 풀어 쓰면 다음과 같다.
final_func = deco1(1)(print_hello)
final_func()
  • 연습 문제 : 파라미터가 있는 데코레이터를 이용해 아래와 같은 결과값을 출력해보자.
# 결과값

<i>안녕하세요.</i>
<h1>안녕하세요.</h1>
<h2>안녕하세요.</h2>
<h3>안녕하세요.</h3>
<h4>안녕하세요.</h4>
<h5>안녕하세요.</h5>
<h6>안녕하세요.</h6>
# 정답 코드

def mark_html(tag):
    def outer_wrapper(func):
        def inner_wrapper(*args, **kwargs):
            print('<{0}>{1}</{0}>'.format(tag, func(*args, **kwargs)))
        return inner_wrapper
    return outer_wrapper

tag_list = ['b', 'i', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6']

for tag in tag_list:
    @mark_html(tag)
    def print_title(title):
        return title
    print_title("안녕하세요.")
Comments