파이썬 코드를 보다보면 가끔 함수 이름 위에 @로 시작하는 구문이 붙어 있는 것을 가끔 볼 수 있다. 

@verbose

def my_function():

print "hello, world."

만약 자바를 주로 사용하는 프로그래머라면 어노테이션(annotation)이랑 비슷한 것인가 하는 생각이 들 것이다. 하지만 미리 말하자면 파이썬의 데코레이터와 자바의 어노테이션은 많이 다르다. 


파이썬의 데코레이터는 그 이름에서 알 수 있듯이 기본적으로는 본체를 꾸며주는 역할을 한다. 위 예제에서는 my_function()함수가 본체에 해당한다. 즉, @verbose라는 데코레이터를 추가함으로서 본체인  my_function()에 뭔가가 더해지기는 했지만, 기본적으로  my_function()이 바뀌는 것은 아니라는 의미이다[각주:1]


파이썬에서 함수는 first citizen에 속한다. 이 말은 파이썬에서 함수를 다른 함수의 인자로 넘기거나 함수의 리턴 값으로 사용하는 것이 가능하며 매우 자연스러운 일이라는 뜻이다. 다음 코드와 같이 말이다.

def verbose(func):

print "Begin", func.__name__;

func();

print "End", func.__name__;



verbose()는 다른 함수를 인자로 받는다. 물론 C에서도 함수 포인터를 이용해 동일한 기능을 이용할 수 있다. 하지만, C는 어디까지나 포인터를 인자로 사용하는 것이지, 함수 자체를 인자로 사용하는 것은 아니다. 즉, C에서 함수는 first citizen이 아니다. 함수가 first citizen인 언어에서는 이를 이용한 다양한 기법들이 있으며, 이를 통해 다양하고 기발한 코딩이 가능하다. 여기서 설명하는  데코레이터도 그 중에 하나이다.


verbose()에 my_function를 인자로 넣으면 다음과 같은 실행 결과를 볼 수 있다.

>>> verbose(my_function)

Begin my_function

hello, world.

End my_function

간단한 예제이지만 우리는 이를 통해서 다른 함수를 인자로 받는 함수를 살펴보았다.  여기서 verbose()는 my_function() 자체의 동작을 변경시키지 않는다. 다만, my_function()의 앞 또는 뒤, 위 경우에는 둘 다,에 약간의 장식을 덧붙였을 뿐이다.


그런데, 이 방법은 엄밀히 말하면 my_function()에 장식을 붙인 것이라 하기 힘들다. 왜냐하면 우리가 호출한 함수는 my_function()이 아니라 verbose()이기 때문이다. 즉 클라이언트 입장에서는 my_function()에 장식이 덧붙여진 것이 아니라, 원래 저런 기능을 하는 verbose() 함수를 사용한 셈이다.


verbose()를 수정해서 이 문제를 해결해보자.

def verbose(func):

def new_func():

print "Begin", func.__name__;

func();

print "End", func.__name__;

return new_func;


verbose()에서  new_func()이라는  nested function을 정의한 다음에 마지막에 이 함수를 반환한다. 이것은 앞서 말한 바와 같이 파이썬에서는 함수가 first citizen이므로 이러한 코드는 전혀 문제가 없다. new_func()이 하는 일은 기존의 verbose()와 동일하다.


이제 바뀐 verbose()가 my_function()을 어떻게 장식해주는지 보자.

>>> my_function = verbose(my_function)

>>> my_function()

Begin my_function

hello, world.

End my_function

먼저  verbose()의 리턴값으로 my_function을 대체한다. 이렇게 하면 my_function()은 원래 기능과 함께  verbose()에서 제공하는 부가적인 기능까지 수행하는 새로운 함수가 된다. 즉,  verbose()가 my_function()에 장식을 달아준 것이다. 그리고 이 장식은 계속 유지된다.


파이썬의 데코레이터는 사실 위 기능을 좀 더 직관적이고 간결하게 명시할 수 있도록 해주는 syntactic sugar일 뿐이다. 이 글의 첫번째 코드와 같이 함수 정의 윗부분에 데코레이터를 달면(데코레이션) 우리가 위에서 했던 "my_function = verbose(my_function)" 부분을 따로 쓸 필요도 없어지고 코드의 의도도 명확해진다. 주의할 점은 데코레이터는 결국 함수, 좀 더 정확하게 말하면 callable object이므로 당연히 데코레이션을 하기 전에 해당 함수 또는 클래스를 정의해야 한다는 것이다.


클래스 이야기가 나온 김에 클래스로 데코레이터를 정의하는 방법을 살펴보도록 하자. 클래스를 함수처럼 호출되게 하려면 __call__() 맴버 함수를 정의하면 된다. 이를 이용해서 위  verbose()함수를 클래스로 바꿔보면 다음과 같다.

class Verbose:

def __init__(self, f):

print "Initializing Verbose."

self.func = f;


def __call__(self):

print "Begin", self.func.__name__

self.func();

print "End", self.func.__name__


@Verbose

def my_function():

print "hello, world."


print "Program start"

my_function();



앞서 사용했던 verbose()와 구별을 위해, 그리고 일반적인 코딩 규약에 따라 클래스 버전은 대문자 V로 시작한다. 코드 자체는 간단하기 때문에 설명할 것은 별로 없다. Verbose가 언제 초기화 되는지를 확인하기 위해 생성자에 메시지를 출력하도록 하였으며, __call__() 맴버 함수에서 return 부분은 필요치 않다. 이 예제를 파일로 저장한 다음 파이썬으로 실행해보면 다음과 같은 결과를 볼 수 있다. 여기서는 파일 이름을 decorator_test.py라 하였다.

$ python decorator_test.py

Initializing Verbose

Program start

Begin my_func

hello, world

End my_func


결과를 보면 알 수 있듯이 Verbose 클래스의 생성자가 먼저 실행된 다음에 프로그램의 메인 부분이 실행된다. 이를 통해 우리는  데코레이터가 함수 정의 단계에서 실행됨을 알 수 있다.  그 밖에는 앞에서 이야기 한 것과 동일하다.


마지막으로 인자를 갖는 함수에 대한 데코레이터를 만드는 방법에 대해 이야기해보자. 지금까지의 과정과 마찬가지로이것 역시 파이썬답게 간단하다. 우리가 할 일은 단지 '*args'와 '**kwargs'만 추가해주면 된다[각주:2].

class Verbose:

def __init__(self, f):

print "Initializing Verbose."

self.func = f;


def __call__(self, *args, **kwargs):  #여기 

print "Begin", self.func.__name__

self.func(*args, **kwargs);    #여기

print "End", self.func.__name__


@Verbose

def my_function(name):   #l 인자를 갖는 함수

print "hello,", name




참고 자료



  1. 이 정도 의미로 생각한다면 자바의 어노테이션도 동일한 것이라 볼 수도 있다. 하지만 자바의 어노테이션은 파이썬에 비해 자신이 원하는 대로 만들어 사용하기가 매우 어렵다. [본문으로]
  2. *args, **kwargs 는 파이썬에서 일종에 가변 길이 인자를 다루는 방법이라 할 수 있다. 개인적으로는 파이썬 문법 중에서 가장 난해한 부분이 이 *args, **kwargs라고 생각한다. 사실 어려운 내용은 아니지만 직접 쓸 일이 없다 보니 매번 까먹는 것 같다. [본문으로]

WRITTEN BY
trowind
자연어처리, 프로그래밍, 여행, 음식, 삶의 기록

트랙백  0 , 댓글  3개가 달렸습니다.
  1. 잘 보고 갑니다.
  2. rakk4403 2016.01.01 21:10
    잘봤습니다. 좋은포스팅 감사합니다.
  3. 잘보고갑니다.
secret