Metaclass를 설명하기에 앞서 Python에서의 class란 무엇인가를 먼저 좀 짚고 넘어갈게요. 보통 다른언어에서 class를 정의하면 그 자체로는 객체(instance)가 될수 없고 오직 ClassName()이렇게 괄호를 사용해서 구현했을때만 instance를 얻어낼수가 있자나요. 그런데 Python에서 class는요 smalltalk이라는 언어에서 그 개념을 가져왔는데요. 얘는요 class를 정의하는 순간, instance가 만들어집니다. 여기서 instance의 개념을 좀 정의하고 넘어갈 필요가 있는데요. 일반적으로 memory에 class구조에 해당하는 어떤 공간을 할당했다라고 하면 그걸 보통 객체, instance라고 하자나요? 그런데 Python에서는 instance의 개념이 좀 헷갈리는게 얘가 class를 선언하면서 바로 memory에 공간을 할당해버리기때문에 class자체가 instance같이 존재하게 되어버리는거에요. 그렇다고 해서 그러면 Python은 class를 instance구현없이 그냥 막바로 쓰느냐? 또 그건 아니에요. class는 class대로 memory에 모양을 갖춰 instnace처럼 존재하지만 이걸 그냥 막 여기저기서 써버리면 class가 더러워져버리니까 실제 코딩을 할때는 그렇기 안하고, class는 class자체로 순수하게 냅두고 개발자가 필요에 따라 class의 instance를 별도로 만들어서 그거가지고 지지고 볶도록 하는거죠. 그러니까 여기서 용어를 좀 정리를 할게요. 보통 일반적으로 class가 메모리에 할당이 된 상태를 instance의 형태로 존재한다고 말하는데, 우리는 그 상태를 두가지로 분류할수 있어요. 처음에 class가 선언되면서 class자체가 memory에 할당이 되면서 instance랑 똑같은 모양으로 선언되는 거랑, 실제로 개발자가 ClassName()를 호출해서 class를 변수에 할당해서 일반적으로 우리가 일컫는 instance를 만들어서 사용하는 그런 경우 이렇게요. 이게 좀 헷갈리니까 처음에 class를 선언할때 얘가 memory에 들어가 있는 상태는 일단 instance가 되었다고 표현하지 않을게요. instance라는 영어의 뜻자체는 class를 구현했을때 만든 복사본을 의미하는것에 더 가깝거든요. 그런데 우리가 class를 구현했을때 memory에 할당이 되는것을 알고 있기때문에 어쩌다 그 의미가 “class가 memory에 할당된 상태”라고 와전이 되버린거죠. 그래서 Python에서 class가 선언되면서 memory에 할당이 되는걸보고 사람들이 Python에서는 class가 선언되면서 instance의 형태를 갖는다라고 말하게 된거구요.
이제 그러면 metaclass에 대해서 얘기를 해볼게요. 위에서 설명드린 class와 instance에 대한 용어정리는요 metaclass를 이해하기 위해 꼭 필요한건 아니구요 metaclass와 class, 그리고 instance의 상관관계를 설명드릴때 부연잡지식으로 좋을거같아서 그냥 서론이 좀 길었던거구요. 그러면 본론으로 들어가서 metaclass가 뭐냐면요, class의 엄마같은 애에요. 그동안 class가 최고봉이고 오리지날 이라고 생각했는데 그 위에 뭐가 더 있었다니 좀 신기하게 생각하시는 분들이 계실것같은데요. 아래에 제가 보여드리는 예제를 보시면 이미 알고 계셨는데 그동안 간과하고 있었구나 하고 깨닫게 되실거에요. 일단 아래와 같이 클래스를 하나 선언할게요.
>>> class Foo:
... pass
그리고 나서 이 클래스의 type이 몬가 하고 들여다보면요
>>> type(Foo)
<class 'type'>
type이 type
이라고 나와요. class의 type은 type
이네요. 이번에는 class말고 data type들의 type을 한번 확인해볼까요?
>>> type(int)
<class 'type'>
>>> type(list)
<class 'type'>
>>> type(tuple)
<class 'type'>
어라? data type의 type도 type
입니다. 이게 Python3에서 새로 도입된 개념인데요. 기존에 분리되어있었던 type과 class의 영역을 type
으로 통합해버린거에요. 그래서 class도 그냥 어떤 data type의 하나로 분류를 하게 된거죠.
옛날에는요 class랑 type이 서로 달랐어요. Python2에서는요 class의 type을 찍어보면 classobj
라고 나와요.
>>> # Python 2
>>> class Foo:
... pass
>>> type(Foo)
<type 'classobj'>
그런데 Python3이 되면서 class를 data type처럼 어떤 type의 한종류로 보고 type과 class의 개념을 혁신적으로 통합을 해버린거죠.
>>> class Foo:
... pass
>>> type(Foo)
<type 'type'>
사실 이 개념은 Python 2.2부터 있어왔는데요 그때당시에는 이렇게 쓰겠다 하고 특별히 명시를 해주지 않으면 default가 classobj로 되어 버리기때문에 그때는 확실히 그렇게 쓰였다고 보기는 힘들구요 Python 3부터 이 방식이 default로 사용되었기때문에 있었던거지만 2.2에서보다는 3에서 더 혁신적이라고 할수 있죠
아직도 살짝 헷갈려하시는 분들을 위해서, 이번에는 그 차이점을 instance에서 한번 확인해볼까요? 아래의 코드는 Python 2에서 class를 instance로 구현해서 __class__와 type을 비교해본 코드입니다.
>>> # Python 2
>>> obj = Foo()
>>> obj.__class__
<class __main__.Foo at 0x000000000535CC48>
>>> type(obj)
<type 'instance'>
Python 2에서는 __class__를 찍어보면 나오는게 “<class”로 시작하고, type함수를 통해서 찍어보면 “<type”으로 시작하는 결과가 나옵니다. 이게 무슨의미 인가요? class는 class고 type은 type으로 Python 2에서는 이걸 전혀 다르게 핸들링을 하다가 Python 3에서 이걸 type과 class로 전부다 통합하고 두개의 개념을 계층으로 표현하도록 한거에요.
이해를 돕기위해서 Python 3에서는 같은 코드가 어떻게 보이는지 한번 확인해볼까요?
>>> obj = Foo()
>>> obj.__class__
<class '__main__.Foo'>
>>> type(obj)
<class '__main__.Foo'>
이번에는 두개다 “<class”로 시작하고 있죠? 이제 그럼 두개가 같은건가요?
>>> obj.__class__ is type(obj)
True
네 이제 __class__는 해당 instance가 어떤 type인지에 대한 정보를 가지고 있고, type함수의 결과와 동일합니다.
여기서 그러면 다른 data type의 instance들은 각각 어떤 type을 가지는지 궁금해지죠?
>>> type(int)
<class 'type'>
>>> type(3)
<class 'int'>
>>> type(list)
<class 'type'>
>>> type(['foo', 'bar', 'baz'])
<class 'list'>
>>> type(tuple)
<class 'type'>
>>> t = (1, 2, 3, 4, 5)
>>> type(t)
<class 'tuple'>
>>> class Foo:
... pass
...
>>> type(Foo)
<class 'type'>
>>> type(Foo())
<class '__main__.Foo'>
제가 기억을 상기시켜드리기 위해서 아까 찎어봤던 각 data type의 type을 한번에 정리해봤어요. 보시면, int라는 data type의 type을 확인해봤을때는 type
라는 class이고 이걸 구현한 정수값의 type을 확인해보면 int
라는 class라고 나옵니다. 일단 주목하셔야하는 부분이요. 이제 더이상 “<type”으로 시작하는 애는 없어요. 이제 Python에서 모든 자료구조는 class입니다. 그리고 계층구조가 보이시나요? class를 포함한 data type들은 type
이라는 class로 만들어져 있고, 그 class들을 구현해서 만들어진 instance들은 각각의 class이름을 가지죠. int, list, tuple 혹은 __main__.Foo와 같은 class이름을 type으로 가져요.
여기 metaclass뭔지 배우러왔는데 자꾸 딴소리만하고 도대체 metaclass가 몬가요? 여기서 보시는 type
이라는 class가 바로 metaclass입니다. type
이라는 큰 덩어리에서 int, list, tuple등 각 class들이 파생되는거죠.
다시 정리해볼게요. Python에서 모든것들은 class로 이루어져있습니다. int, list, tuple 또는 MyClass같은 class들은 이제 더이상 그냥 기존에 우리가 생각하고 있던 class가 아니고 type
이라는 class에서 파생된 class라고 할수 있죠. 아니, 파생되었다기보다 type이라는 스타일의 class들이라고 하는게 더 정확한 표현이 될것 같아요. 1,2,3들이 int
라는 스타일의 instance들이 되는것 처럼 말이죠. 객체를 만든다고 할때 우리 “구현”한다라고 표현하는데, 구현이 무슨 의미인지는 다 알고 계시죠? 의상디자이너가 패턴을 그리고, 누군가 패턴가지고 옷을 만들면 이때, 패턴을 옷으로 구현하다라고 하죠? 건축가가 설계도를 그리고, 누군가 그 설계도를 가지고 건물을 지으면 그것도 “구현”한게 된거에요.
위의 그림을 보시면요 instance는 class를 구현한거에요. 그리고 class는 metaclass를 구현한겁니다. 예를 들어보자면 아래와 같은 그림들이 나오겠죠?
대충 metaclass가 어떤 것인지 흐릿하게 감이 오시죠? 다음시간에 metaclass와 상속의 차이점에 대한것을 공부하고, 그 후에 custom metaclass을 직접 만들어보시면 그 개념이 더확실해지지 않을까 기대해봅니다. 여기까지 읽어주셔서 감사하고요 다음 강의도 꼭 읽어주세요. 그럼 좋은 하루 되세요.
References: