[Python] 데코레이터에 대한 고찰 Develop Tip

파이썬에서 어떤 클래스에서 클래스 인스턴스 함수를 만들때는 항상
첫번째 패러미터로 self가 옵니다.

myCls:
myF1(self, p1,p2):
print 'in myF1(p1="%s",p2="%s")' % (p1,p2)

와 같은 경우 myF1의 첫번째 패러미터로 항상 self가 옵니다.
C++, C# 등에서와 같이 this 함수와 같은 의미로 이해하면 되겠지요.

헌데 다른 OOP의 경우에서 myCls 클래스의 static 함수개념이 있을 수 있습니다.
이런 경우는 

myCls:
myF2(p1,p2):
print 'in myF2(p1="%s",p2="%s")' % (p1,p2)

위의 myF2 함수와 같이 첫번째 패러미터로 self 가 존재하지 않습니다만,
여전히 p1으로 self 개체가 넘어가는 오류가 발생합니다.
이런 경우는

myCls:
@staticmethod
myF2(p1,p2):
print 'in myF2(p1="%s",p2="%s")' % (p1,p2)

와 같이@staticmethod 라는 것을 이용하는데요,
이런 식으로 함수 앞에다 무언가 @ 로 시작하는 무언가를 호출하는 것을
파이썬에서 데코레이터라고 합니다.

PEP (Python Enhancement Proposal) 318에서 제안되어 있더군요.

이것이 어떻게 동작하는지 제대로 이해하지 못하고 있다가
이번에 알게 되었습니다.

그에 앞서 다음과 같은 get_param 이라는 함수를 살펴보겠습니다.

#==========================================================================
def get_param(*args, **kwargs):
sl = []
for arg in args:
if isinstance(arg,str) or isinstance(arg,unicode):
sl.append('%s:"%s"' % (type(arg).__name__,arg))
else:
sl.append('%s:%s' % (type(arg).__name__, arg))
for k,v in kwargs.items():
if isinstance(v,str) or isinstance(v,unicode):
sl.append('%s=%s:"%s"' % (str(k),type(v).__name__,v))
else:
sl.append('%s=%s:%s' % (str(k),type(v).__name__,v))
return ','.join(sl)

파이썬에서 모든 패러미터는 unnamed parameter와 named parameter로 구분되는데,
모든 가변 변수는 위와 같이 (*args, **kwargs) 로 모든 변수를 받아 볼 수 있습니다.
위의 get_param은 그런 모든 패러미터를 출력하는 것입니다.

다음에는,

###############################################################################
def entryExit(original_function):
def new_function(*args, **kwargs):
print ">>>Entering", original_function.__name__, '(%s)' % get_param(*args, **kwargs)
original_function(*args, **kwargs)
print "<<<Exited", original_function.__name__
return new_function

라는 함수를 하나 만들었습니다.

패러미터로 받은 original_function은 함수 포인터와 유사한 개념이라고 할 때,
그 안에 선언된 new_function은 일반 패러미터를 받아 출력하고 원본 함수를 다시 호출하는 것입니다.

그러면 다음과 같이 데코레이터로 이용할 수 있습니다.

@entryExit
def hello(targetName=None):
if targetName:
print("\tHello, " +  targetName +"!")
else:
print("\tHello, world!")

또한 위와 같이 데코레이터를 가진 hello를 호출하면,

hello("Earth")

>>>Entering hello (str:"Earth")
Hello, Earth!
<<<Exited hello

라고 결과가 나타납니다.

어떤 패러미터를 넣던지 위와 유사한 결과를 나타내줍니다.
위와 같은 것을 함수 데코레이터라 말합니다.

다음은,

class EntryExit(object):
def __init__(self, f):
self.f = f
def __call__(self, *args, **kwargs):
print ">>>Entering", self.f.__name__, '(%s)' % get_param(*args, **kwargs)
self.f(*args, **kwargs)
print "<<<Exited", self.f.__name__

라는 클래스를 만들었습니다.

생성자에 패러미터로 f 라는 함수 포인터가 있다고 가정하고,
무언가가 호출될 때, __call__ 이 호출된다는 개념입니다.

위와 같은 것을

@EntryExit
def func1(*args, **kwargs):
print "\tinside func1(%s)" % get_param(*args, **kwargs)

라고 선언을 하고,

func1(2,'sss',foo='baa')

와 같은 식으로 호출을 하면,

>>>Entering func1 (int:2,str:"sss",foo=str:"baa")
inside func1(int:2,str:"sss",foo=str:"baa")
<<<Exited func1

와 같은 entryExit와 같은 결과를 보여줍니다.
이와 같은 데코레이터를 클래스 데코레이터라고 합니다.

하지만 이는 클래스 안에서 self를 포함하여 호출할 때는 오류가 발생할 수 있습니다.
다음과 같은 코드가 있습니다.

class myClass(object):
def myDecorator(original_function):
def new_function(self, *args, **kwargs):
print ">>>Entering", original_function.__name__, '(%s)' % get_param(*args, **kwargs)
original_function(self, *args, **kwargs)
print "<<<Exited", original_function.__name__
return new_function
@entryExit
def myFuncA(self, a, b, c='default'):
print '\tmyFuncA(a="%s",b="%s",c="%s")' % (a,b,c)
@myDecorator
def myFuncB(self, a, b, c='default'):
print '\tmyFuncA(a="%s",b="%s",c="%s")' % (a,b,c)

그리고 다음처럼,

mc = myClass()

생성을 하고,

mc.myFuncA(1,2)

라고 호출하면,

>>>Entering myFuncA (myClass:<__main__.myClass object at 0xf13650>,int:1,int:2)
myFuncA(a="1",b="2",c="default")
<<<Exited myFuncA

위와 같이 self까지 넘어와서 잘 호출된 것을 확인할 수 있습니다.

하지만 

@EntryExit
def myFuncA(self, a, b, c='default'):
print '\tmyFuncA(a="%s",b="%s",c="%s")' % (a,b,c)

와 같이 @entryExit 대신 @EntryExit를 호출하면 self 를 넘길 수 없어 오류가 발생하는데,
그와 같은 경우에는 위와 같이 self를 포함해서 넘기는 myDecorator와 같이 
함수를 지정하면 잘 동작합니다.

mc.myFuncB(3,'four')

라고 마지막으로 호출을 해 보면,

>>>Entering myFuncB (int:3,str:"four")
myFuncA(a="3",b="four",c="default")
<<<Exited myFuncB

라고 잘 되는 것을 확인할 수 있습니다.


어느분께는 도움이 되셨기를...

핑백

  • 한글이 잘 보이는지..? | parannamu 2015-05-21 05:30:53 #

    ... 해야할일 스핑크스(sphinx)를 이용한 문서작성 : http://mcchae.egloos.com/11080328 데코레이터에 대한 고찰 : http://mcchae.egloos.com/11030528 Pycharm을 이용한 원격디버깅 : http://mcchae.egloos.com/11065987 스케줄러 작업에 대한 고찰 : ht ... more

  • 지훈현서 : [Python] decorator를 이용한 함수의 doc string 구하기 2018-05-11 13:22:05 #

    ... 파이썬에서 데코레이터에 대한 고찰을 확인해 본 적이 있습니다만,아래와 같은 아주 간단한 데코레이터를 이용한 foo 라는 함수가 있다고 가정합니다. def decorator1(f): ... more

덧글

  • 태로군 2013/08/23 20:38 # 삭제 답글

    데코레이터 알듯하면서도 막상코딩할때는 쉽게 써먹기가어렵네요~^^좋은글감사합니다
  • 지훈현서아빠 2013/08/24 08:41 #

    도움이 되셨다니 저의 보람입니다~
  • 2014/08/22 15:10 # 삭제 답글

    쓰기를 대단히 감사합니다.
  • 2014/09/19 07:40 # 삭제 답글

    아주 좋은 게시 할 수 있습니다. 난 그냥 웹 로그 우연히 내가 정말 당신의 웹 로그 게시물을 찾아 사랑했던 말을 바랬다. 어떤 경우에는 내가 당신의 피드에 가입되며, 난 당신이 곧 다시 한 번 쓰기를 바라고있다!
  • 지훈현서아빠 2014/09/19 09:16 #

    아니 구글 번역기 님이 이렇게 글도 하사해 주시고.. ㅎㅎ
댓글 입력 영역

구글애드텍스트