|
저번 시간에는 간단하면서 재밌고 유익한 Python Programming 뿐만 아니라 객체 지향 언어(C++, C#, Java...)에서
흔히 꽃이라고 말하는 "상속"이라는 것을 끝냈습니다.
(와아아아아아아아아 만세!!!)
상속까지 이해하신 여러분들은 일단 객체 지향 Programming이 어떻게 실행이 되고 어떻게 동작이 되는지에 대해서 이해를 하시게 된 것이고, 특히 Python에서 대부분 상속 이후에도 Decorator, Iterator 등 흔히 개발자들 사이에서 통용되는 STL이라는 과정이 있는데, 그 과정을 제외한 Python 객체 지향 Programming에 대해 거의 끝냈다고 보시면 될 것 같습니다.
서론이 길었군요..
이 번 시간에는 "데코레이터"에 대하여 알아보는 시간을 가지도록 하겠습니다.
"데코레이터(Decorator)"는 우리가 흔히 알고 있는 영어 단어 중 하나인 Decoration(데코레이션)과 비슷한 말이며,
파이썬에서는 흔히 '객체가 그 객체 안에 존재하는 함수들을 꾸민다' 라는 뜻풀이를 가지고 있습니다.
(서론이 더 길어지기 전에 여기서!!)
자 그렇다면 왜 데코레이터를 사용하는 것일까요?
데코레이터는 객체가 그 객체 안에 존재하는 함수들을 꾸민다는 말인 "함수의 장식"이라고도 많이 통용되지만, 데코레이터는 본질적으로 함수를 '대리 호출'하여 여러 번 호출하는 번거로움을 막기 위함이라고 보시면 될 것 같습니다.
참고로 데코레이터는
@staticmethod @classmethod @singleton @decorator... 등 아름답고 깔끔하며, 또한 깨끗하게 구현할 수도 있답니다.
데코레이터는 참 신기합니다. 왜냐하면 __init__() Method(메소드)의 매개변수를 통해 함수나 Method를 넘겨받아 데이터 Attribute(속성)에 저장해두는 것 뿐만 아니라 클래스에 부가적으로 목록을 추가할 수 있다는 장점이 있죠. 한 번 하단의 코드를 보시겠습니다.
## Class를 함수처럼 호출해주는 마법같은 Code!!!
class MyHumanDeco:
def __init__(self, Human, Head, Body, Leg):
print("Init...")
self.human = Human
self.head = Head
self.body = Body
self.leg = Leg
def __call__(self):
print("Start From Human...")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Legs: {0}".format(self.leg.__name__))
self.human()
self.head()
self.body()
self.leg()
print("End From Human...")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Leg: {0}".format(self.leg.__name__))
def MyHuman():
print("Human!!!")
def MyHead():
print("Head!!!")
def MyBody():
print("Body!!!")
def Myleg():
print("Legs!!!")
MyHuman = MyHumanDeco(MyHuman, MyHead, MyBody, Myleg)
MyHuman()
코드를 보다가 한 번 특이한 것을 찾았습니다.
바로 굵은 글씨로 표시된 부분입니다. MyHuman이라는 객체를 MyHumanDeco로부터 생성하며, MyHumanDeco에는 각각 위치함수로 정의된MyHuman(), MyHead(), MyBody(), MyLeg()라는 함수들을 MyHumanDecorator의 객체(Object)(Class)를 가리키게 됩니다. 쉽게 하자면 MyHuman, MyHead, MyBody, Myleg라는 각각 Class __init__함수의 매개변수에 맞춰서 각각 한 개씩 함수들을 만들었는데, 그 함수들이 모두 각각의 Class 매개변수를 가리키고 있다는 것입니다. .
또한 데코레이터는 원래 가리키고 있던 함수 코드가 완전히 소멸된 것은 아닌 것이고도 볼 수 있습니다.
그 이유는 아직도 MyHumanDeco라는 Class안에 생성은 되었으나 아직 소멸되지 않고 살아 있기 때문이며,
__call__() Method를 구현한 MyHumanDeco의 객체(Instance)이기 때문에 함수처럼 호출하는 것도 가능해지는 것이죠.
이와 같이 매개변수들을 사용할 때마다 일일이 추가를 한다면 매우 불편하고 귀찮은 작업이 될 것입니다.
이를 해결하고자 Python Decorator에서는 Class __init__() 의 수 많은 매개변수들을 단 한 개의 매개변수와 단 한 개의 @decorator 메소드를 사용하여 class __init__()에 존재하는 수많은 멤버 변수(들을 각각에 맞는 매개변수를 추가하지 않아도 단 한 개의 멤버 변수로 연결하여 사용할 수 있습니다.
이제 본격적으로 Decorator의 @기호를 사용하여 만들어보겠습니다.
데코레이터는 @decorator과는 다른 모습을 하고 있는 데코레이터의 생성자를 이용할 수 있다는 것입니다.
데코레이터는 특히 클래스의 경우에선 위와 같이 데코레이터의 인스턴스를 만들어 사용합니다.
바로 앞에서는 Decorator에 Class 객체(Instance)를 통해 만들어 여러모로 불편했지만, @ 기호로 시작하는 Decorator 전용 문법을 이용하면 함수에 꽃 장식을 꽂아 놓듯 간단하게 Decorator의 Instance를 만들 수 있습니다.
한 번 위에 있는 Code와 @ 기호를 활용하여 새롭게 만들어 보았습니다.
# Class를 함수처럼 호출해주는 마법같은 Code!!!
class MyHumanDeco:
def __init__(self, Human, Head, Body, Leg):
print("Init...")
self.human = Human
self.head = Head
self.body = Body
self.leg = Leg
def __call__(self):
print("Start From Human...")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Legs: {0}".format(self.leg.__name__))
self.human()
self.head()
self.body()
self.leg()
print("End From Human...")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Leg: {0}".format(self.leg.__name__))
@MyHumanDeco
def MyHuman():
print("Human!!!")
@MyHumanDeco
def MyHead():
print("Head!!!")
@MyHumanDeco
def MyBody():
print("Body!!!")
@MyHumanDeco
def Myleg():
print("Legs!!!")
MyHuman()
MyHead()
MyBody()
Myleg()
겉으로 보았을 때에는 큰 문제가 없을 것 같은데 이와 같이 코드를 작성하면 위 Class에 존재하는 4개의 매개변수 중 3개의 매개변수가 함께 등록되지 않는 상황이 발생합니다. 아주 심각한 오류 중 하나, TypeError가 발생하는 것이죠
이와 같은 오류가 발생하는 이유는 @MyHumanDeco라는 Decorator(@)는 MyHumanDeco __init__() 함수에 1개의 매개변수만 받을 수 있는데, 1개를 초과한 개수인 4개가 함께 등록이 되었으므로, 맨 처음에 등록한 멤버 매개변수인 Human 만 등록하고, Head, Body, Leg라는 또 다른 멤버 매개변수들을 포함 시키지 않았기 때문에 다음과 같은 오류가 발생하는 것입니다.
또한 @MyHumanDeco라는 Decorator는 한 번만 Class를 생성해도 되는데, 여러 번 Class들을 계속 위에 덮어 씌우듯이 생성을 하면 오류가 발생합니다.
그렇다면 @MyHumanDeco라는 Decorator를 한 번만 불러오면 어떻게 되는지 한 번 살펴보겠습니다.
# Class를 함수처럼 호출해주는 마법같은 Code!!!
class MyHumanDeco:
def __init__(self, Human, Head, Body, Leg):
print("Init...")
self.human = Human
self.head = Head
self.body = Body
self.leg = Leg
def __call__(self):
print("Start From Human...")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Legs: {0}".format(self.leg.__name__))
self.human()
self.head()
self.body()
self.leg()
print("End From Human...")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Leg: {0}".format(self.leg.__name__))
@MyHumanDeco
def MyHuman():
print("Human!!!")
def MyHead():
print("Head!!!")
def MyBody():
print("Body!!!")
def Myleg():
print("Legs!!!")
MyHuman()
MyHead()
MyBody()
Myleg()
이 번 코드는 거의 다른 것이 없습니다. @MyHumanDeco라는 Decorator를 한 번만 불러왔습니다. 여기서 Decorator를 한 번만 호출했다는 뜻은 위에서 설명했다시피 Class를 한 번만 생성했다는 뜻으로 이해하시면 될 것 같습니다.
그러나 그래도 오류 내용은 달라지지 않습니다.
위에서 설명을 드렸다시피 @MyHumanDeco라는 Decorator는 1개의 멤버 매개변수만 받을 수 있는데, 1개라는 숫자의 범위를 훨씬 뛰어넘은 4개를 불러왔기 때문에 오류가 발생한 것입니다.
그렇다면 1개의 멤버 매개변수 만을 받아와서 만들어 볼까요?
class MyHumanDeco():
def __init__(self, Human):
print("Initializing..")
self.human = Human
self.head = Human
self.body = Human
self.leg = Human
def __call__(self):
print("Starting...")
print("Human: {0}".format(self.human.__name__))
print("Human: {0}".format(self.head.__name__))
print("Human: {0}".format(self.body.__name__))
print("Human: {0}".format(self.leg.__name__))
self.human()
self.head()
self.body()
self.leg()
print("Ending..")
print("Human: {0}".format(self.human.__name__))
print("Head: {0}".format(self.head.__name__))
print("Body: {0}".format(self.body.__name__))
print("Leg: {0}".format(self.leg.__name__))
@MyHumanDeco
def MyHuman():
print("Human")
def MyHead():
print("Head")
def MyBody():
print("Body")
def Myleg():
print("Legs")
MyHuman()
MyHead()
MyBody()
Myleg()
이 번에는 별 오류가 없이 잘 실행이 될 것입니다.
그 이유는 @MyHumanDeco라는 Decoration는 Class 함수 매개변수를 가리키는 Class 함수 내부에서 생성된 멤버 변수를 가리킬 때 사용하는 self와 그리고 사용자가 직접 정해서 사용하는 한 개의 매개변수 만을 받아서 사용할 수 있는데, 사용자 지정 매개변수의 개수가 2개 이상이 된다면 오류가 발생하기 때문입니다.
다행히 Class 함수 내부에서 생성된 멤버 변수가 한 개의 Class 함수 매개 변수를 통해 생성되므로, Code에 큰 문제는 없습니다.
결론
1. 데코레이터(Decorator)는 Class 또는 객체 안에 존재하는 함수를 꾸며주는 장식 역할을 합니다.
2. @ 를 사용하지 않고 데코레이터를 사용할 경우, 매개변수를 일일이 한 개씩 추가를 해야 하는 단점이 존재하지만, @ 기호를 사용하여 만드면 한 개의 매개변수로 여러 개의 멤버 변수들을 가리켜서(생성해서) 사용할 수 있습니다.
3. @ 기호를 사용할 때 반드시 한 개의 매개변수 만을 사용해야 한다. 안 그러면 함께 생성한 다른 매개변수들을 인식 못해서 오류가 발생할 수 있습니다.
4. @ 기호를 사용해서 Class 하단에 있는 함수들을 생성할 때, 함수 제목들 위에 적을 필요 없이(적으면 결론 3번과 같은 오류가 발생하기 때문에 금지한다.) 한 번만 @ClassName(Class 이름)을 호출하면 됩니다.
(최종 수정: 2019년 4월 26일 오전 7시 46분 10초)