Python이 제공하는 Decorator는 기존의 function을 수정하지 않으면서 특정 처리를 추가할 수 있게 해주는 도구라고 할 수 있다.
Decorate의 "꾸미다"라는 의미에 맞게 기존 function을 꾸며주는 기능을 제공한다.
Decorator를 사용하면 코드 중복을 효과적으로 줄여 보다 간결한 형태의 코딩이 가능함.
- 특히 Python에서는 @를 통한 decorator 사용문법을 통해 매우 간결한 코딩이 가능함.
- 특정 공통된 처리의 모듈화가 아주 효과적으로 가능하게 해줌.
- 단점은 wrapper의 사용으로 debuging이 좀 까다로워짐(에러 위치를 찾는게 좀 더 까다로워짐.)
- Closuer처럼 지나치게 많이 사용시 가독성이 떨어짐.
참고로 이 문서에서는 function으로 decorator를 구현하는 방법을 설명한다.
class로 구현하는 경우는 다음 문서를 참고할 것.
2023.08.18 - [Python] - [Python] Class로 수행시간 측정 decorator 만들기
Applications
Class에서 method를 정의할 때, method head위에 놓여있던 @staticmethod
, @classmethod
, @abstractmethod
등이 바로 decorator 기능이며, 이들은 아래의 method에 특정 기능을 부가해준다.
class Test:
@staticmethod
def s_func():
print('s_func is a static method!')
return
이는 function에서도 사용가능하며 django
의 built-in login_required
decorator 등이 좋은 예이다.
from django.contrib.auth.decorators import login_required
@login_required
def post_detail(request: HttpRequest, pk: int) -> HttpResponse:
# post = Post.objects.get(pk=pk)
post = get_object_or_404(Post, pk=pk) # 해당 object가 없으면 404 page로.
return render(request, "blog/post_detail.html", {"post": post})
Decorator 구현하기 (전처리와 후처리 추가) : for Function
다음은 간단한 곱셈을 수행하는 function mul
의 전 후에 특정 문자열이 출력되는 처리를 추가한 decorator의 예임.
- 아래에 보이듯이 fist-class object로서의 function을 이용한 closure 로 구현이 되며 nested function인 warp_func를 통해 전처리와 후처리등이 추가될 수 있음.
- nonlocal scope의 변수들을 통해 전역변수의 사용없이 특정 상태 등을 기억하게 할 수 있음.
def pre_post_deco(func):
func_call_cnt = 0
# nested function.
def wrap_func(a,b): # 감싸주는 func 와 같은 parameters를 가짐.
print('pre-processing')
nonlocal func_call_cnt
func_call_cnt += 1
print(f' - # of call :{func_call_cnt}')
ret_v = func(a,b)
print('post-processing')
print('===================')
return ret_v
return wrap_func
@pre_post_deco
def mul(a,b):
return a*b
@pre_post_deco
def add(a,b):
return a+b
print(mul(5,2))
print(mul(5,3))
print(mul(5,4))
print(add(1,1))
# ===============================
# 다른 방식으로 decorator 활용하기.
def other_add(a,b):
return a+b
wrapped_func = pre_post_deco(other_add)
print(wrapped_func(2,3))
mul
과add
를 감싸고 있는wrap_func
function이mul
과 같은 수의 parameters를 가지도록 한다.func_call_cnt
는 closure를 활용하여@pre_post_deco
로 감싸진 특정 function이 몇차례 호출되었는지를 기억함.mul
과add
는 각각wrap_func
로 감싸지며 이들은 고유의func_call_cnt
를 가지는 것을 주의할 것.
아래의 다른 방식으로 decorator 활용은 @pre_post_deco
를 function head 위에 붙여놓을 경우 실제 이루어지는 처리를 코드로 구현한 것임. 감싸진 function을 반환받고 이를 호출하는 방식이 실제 decorator가 동작하는 방식임.
결과는 다음과 같음.
pre-processing
- # of call :1
post-processing
===================
10
pre-processing
- # of call :2
post-processing
===================
15
pre-processing
- # of call :3
post-processing
===================
20
pre-processing
- # of call :1
post-processing
===================
2
pre-processing
- # of call :1
post-processing
===================
5
Parameter를 가지는 Decorator
parameter를 가지는 decorator의 경우, 해당 parameter에 따라 다른 동작을 수행할 수 있기 때문에 좀더 활용도가 높음.
위의 예에서 덧셈과 곱셈에 따라 다른 전처리를 해주기위해 pre_post_deco
를 감싸는 pre_post_deoc_w_param
을 통해,
parameter를 가지는 decorator를 구현한 예는 다음과 같음.
def pre_post_deco_w_param(target_func_str): # decorator에 필요한 parmaeters!
# closure에서처럼, target_func_str은 nested functions에서 접근가능함.
def pre_post_deco(func):
func_call_cnt = 0
# nested function.
def wrap_func(a,b): # 감싸주는 func 와 같은 parameters를 가짐.
print(f'{target_func_str}: pre-processing')
nonlocal func_call_cnt
func_call_cnt += 1
print(f' - # of call :{func_call_cnt}')
ret_v = func(a,b)
print('post-processing')
print('===================')
return ret_v
return wrap_func
return pre_post_deco # 실제적인 deco 함수객체 반환.
@pre_post_deco_w_param('multiplication')
def mul(a,b):
return a*b
@pre_post_deco_w_param('addition')
def add(a,b):
return a+b
print(mul(5,2))
print(mul(5,3))
print(mul(5,4))
print(add(1,1))
decorator 중첩하기.
여러 decorator 또는 하나의 decorator를 여러차례 중첩하여 사용할 수 있음.
- 결국 함수를 감싸주는 warpper함수를 다시 감싸주는 방식으로 동작함.
다음의 예를 보자.
@ pre_post_deco_w_param('test 01')
@ pre_post_deco_w_param('test 02')
def multiple_deco_func(a,b):
return a-b
print(multiple_deco_func(10,9))
test 01
을 argument로 가지는 경우가test02
를 argument로 가지는 경우를 감싸고 있음.test 02
를 argument로 가지는 경우는 실제 함수인multiple_deco_func
을 감싸게 됨.
위의 코드는 사실상 다음과 같음.
f = pre_post_deco_w_param('test 01') (pre_post_deco_w_param('test 02')(multiple_deco_func))
f(10,9)
결과는 다음과 같음.
test 01: pre-processing
- # of call :1
test 02: pre-processing
- # of call :1
post-processing
===================
post-processing
===================
1
Decorator 구현하기 (전처리와 후처리 추가) : for Method
method라고 큰 차이가 있진 않음.
단, 주의할 점 하나는 method의 첫번째 argument가 self
라는 점을 반드시 기억해야 한다.
이는 method를 감싸주는 함수의 parameter에서 첫번째에 self
가 할당됨을 의미함.
다음 예로 확인하자.
def method_deco(method_obj):
def wrap_method(self, a, b):
print(f'pre-process : {method_obj.__name__}')
r = method_obj(self, a, b)
print(f'post-process : {method_obj.__name__}')
print('===============================')
return r
return wrap_method
class Test:
@method_deco
def ds_pow(self, a, b):
return a**b
o = Test()
print(o.ds_pow(4,5))
결과는 다음과 같음.
pre-process : ds_pow
post-process : ds_pow
===============================
1024
관련 GIST page
https://gist.github.com/dsaint31x/5acf386dbcba2450b388239d61ceedbd
더 읽어보면 좋은 자료들.
2023.07.15 - [Python] - [Python] first-class object (일급객체)
2023.07.15 - [Python] - [Python] Closure
2023.07.15 - [Python] - [Python] scope와 키워드 global, nonlocal
2023.07.15 - [Python] - [Python] Nested Function
https://cjh5414.github.io/wraps-with-decorator/
'Python' 카테고리의 다른 글
[Python] instance methods, class methods, and static methods (0) | 2023.08.20 |
---|---|
[Python] Class로 수행시간 측정 decorator 만들기 (0) | 2023.08.18 |
[Python] PEP 8 : Style Guide for Python Code (0) | 2023.08.04 |
[Python] asterisk * 사용하기 : unpacking, packing (0) | 2023.07.30 |
[Python] while statement, break and continue (0) | 2023.07.28 |