python decorator
16 Apr 2019 | python 파이썬 decorator closureDecorator ?
데코레이터란 특별한 함수나 클래스로 동작한다. 이 기능이 가능한 이유는 파이썬은 First-Class function이기 때문이다. 아래는 First-Class function의 대표적인 특징이다.
- 변수나 데이터 구조안에 담을 수 있다.
- 함수의 파라미터로 전달할 수 있다.
- 반환 값으로 사용할 수 있다.(closure)
- 동적으로 프로퍼티 할당이 가능하다.
Inner function
본격적으로 데코레이터에 들어가기전에 closure의 개념을 살펴볼 수 있는 inner function을 살펴보자. closure란 First-Class function을 지원하는 언어의 네임 바인딩 기술이다. 함수안에 선언된 내부 함수를 반환하고 이를 함수 외부에서 접근이 가능하게 된다.
# closure example
def parent_func(child):
print("Parent")
def first_child():
return "First child"
def second_child():
return "Second child"
if child == 1:
return first_child
else:
return second_child
위 예제에서 부모함수 내부에 선언된 두 개의 자식함수를 조건문에 의해 반환한다. 아래의 예제를 통해 어떤 일이 일어나는지 살펴보자.
>>> first = parent_func(1)
>>> 'Parent'
>>> first
<function __main__.first_child>
>>> first()
'First child'
first 변수에 부모함수에서 반환한 first_child 함수를 참조하고 first를 실행하면 first_child 함수를 실행하게된다. 이러한 특징을 이용해 데코레이터를 구현할 수 있다.
Simple decorator
def simple_decorator(func):
def wrapper():
print('Before the function is called.')
func()
print('After the function is called.')
return wrapper
def say_hello():
print('Hello!')
@simple_decorator
def say_hi():
print('Hi!')
>>> hello = simple_decorator(say_hello)
>>> hello
<function __main__.wrapper>
>>> hello()
Before the function is called.
Hello!
After the function is called.
>>> say_hi
<function __main__.wrapper>
>>> say_hi()
Before the function is called.
Hi!
After the function is called.
say_hello 예제는 First-Class function 특징을 이용해 함수의 파라미터에 함수를 보내고,
내부 함수 반환을 통해 외부(say_hello)에서 내부 함수(wrapper)를 접근하는 것을 구현한 예제이다.
say_hi 예제는 데코레이터를 사용했는데 외부 함수(say_hi)위에 데코레이터를 정의하여 구현한 예제이다.
Decorating functions with arguments
def arg_decorator(func):
def wrapper(*args, **kwargs):
print('wrapper: Hi! {}'.format(args[0]))
func(*args, **kwargs)
return wrapper
@arg_decorator
def say_hi(name):
print('Hi! {}'.format(name))
say_hi에 name변수를 실으면 데코레이터 선언에 의해 arg_decorator 내부 함수인 wrapper의 파리미터로 전달이 된다. 전달된 파라미터는 wrapper 함수내부에서 사용가능하며 이를 이용한 동작이 외부 함수인 say_hi를 통해 일어난다.
>>> say_hi
<function __main__.wrapper>
>>> say_hi('Cole')
wrapper: Hi! Cole
Hi! Cole
Returning values from decorated functions
def return_decorator(func):
def wrapper(*args, **kwargs):
func(*args, **kwargs)
return func(*args, **kwargs) # 내부 함수의 반환으로 외부 함수의 반환값이 동작
return wrapper
@return_decorator
def say_hi(name):
print('Hi! {}'.format(name))
return 'I\'m tyler'
>>> say_hi('Cole')
Hi! Cole
Hi! Cole
I'm tyler
위 코드를 자세하게 이해하기 위해 inspect 모듈을 이용해 frame구조를 살펴보자.
import inspect
frame_return_decorator = None
frame_wrapper = None
frame_say_hi = None
def return_decorator(func):
global frame_return_decorator
frame_return_decorator = inspect.currentframe()
def wrapper(*args, **kwargs):
global frame_wrapper
frame_wrapper = inspect.currentframe()
return func(*args, **kwargs)
return wrapper
@return_decorator
def say_hi(name):
for x in inspect.stack():
print(x)
global frame_say_hi
frame_say_hi = inspect.currentframe()
print('Hi! {}'.format(name))
return 'I\'m tyler'
>>> say_hi('Cole')
(<frame object at 0x7f915b174fb0>, '<ipython-input-86-ee5e8e3c7b70>', 17, 'say_hi', [u' for x in inspect.stack():\n'], 0)
(<frame object at 0x1022b9608>, '<ipython-input-86-ee5e8e3c7b70>', 12, 'wrapper', [u' return func(*args, **kwargs)\n'], 0)
(<frame object at 0x1022b9da8>, '<ipython-input-87-3ebe088a51a6>', 1, '<module>', [u"say_hi('Cole')\n"], 0)
...
Hi! Cole
I'm tyler
>>> frame_return_decorator.f_back
<frame at 0x7f915b3756f0>
>>> frame_wrapper.f_back
<frame at 0x1022b9da8>
>>> frame_say_hi.f_back
<frame at 0x1022b9608>
먼저 stack()함수에 의한 frame의 stack을 살펴보면 return_decorator(wrapper(say_hi))순서의 호출을 확인할 수 있다. say_hi 함수가 stack의 최상위에 있다는 것을 알 수 있다. inspect.currentframe()의 attributes 중 f_back은 caller의 frame을 나타낸다. say_hi의 f_back은 wrapper의 frame과 같은 것을 확인할 수 있다. 즉 say_hi의 반환은 wrapper의 반환이 없다면 값이 최종 프레임에 전달되지 않는다.
Functools decorator
데코레이터를 인터프리터에서 확인해보면 내부함수인 wrapper를 참조하는 것을 확인할 수 있는데, 이는 디버깅시 문제가된다. 따라서 functools 모듈을 이용해서 실제 데코레이터의 함수 정보를 얻어야한다.
import functools
def simple_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
func(*args)
return wrapper
@simple_decorator
def say_hi(name):
print('Hi! {}'.format(name))
>>> say_hi
<function __main__.say_hi>
>>> help(say_hi)
Help on function say_hi in module __main__:
say_hi(*args, **kwargs)
정리
python은 First-class function을 지원하는 언어이다. 이러한 특징으로 closure를 이용해 데코레이터를 구현하다.
이는 특정 함수의 시작 전, 시작 후에 동작하는 로직을 구현할 때 유용하다. 유효성 검사, 로그 관리 등
함수가 가지는 실제 코어 로직과는 별도로 진행되는 역할을 데코레이터를 통해 구현함으로써 코드의 재사용을 줄일 수 있다.