metaclass는 일반적인 파이썬 사용에는 필요없는 조금은 깊이 들어가는 내용이지만, OOP적 접근을 위해 알 필요가 있어보인다. stackoverflow에 달린 설명 참조.
파이썬에서는 모든게 object이다. 클래스도 object로 생성이 되고 instance화 시킬 때, 이 object가 복사되면서 __init__ 루틴을 타게된다. 일반적인 인스턴스에 대해 어떤 클래스로부터 왔는지 알아보는 함수에 type()을 쓸 수 있는데, 그렇다면 클래스는 어떤 타입일까?
class Foo:
pass
x = Foo()
print(type(x))
print(type(Foo))
<class '__main__.Foo'>
<class 'type'>
인스턴스 x의 type은 Foo인데, Foo의 type은 ‘type’이다. type에 대해 좀 더 알아보자.
for tp in int, float, dict, list, tuple:
print(type(tp))
print(type(type))
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
<class 'type'>
위에서 보듯이 기본 자료형들의 type은 모두 ‘type’이다. 심지어 type의 type도 type이다. 이 모든 클래스들의 원형이 되는 클래스이자 인스턴스인 녀석을 metaclass라고 부른다. metaclass의 뜻은 클래스를 만드는데 사용되는 클래스를 의미한다.
잠깐 type의 다른용도에 대해 알아보자. type은 인자를 더 사용하면, 클래스를 만들 수 있다. 메뉴얼 type항목 참조
class type(name, bases, dict)
# name : name of the class
# bases : tuple of the parent class(can be empty)
# attrs : dictionary containing attributes names and values
이는 코드상에서 ‘class’키워드를 만났을 때 하는 것과 동일하며, 다음의 두 코드는 같은일을 한다.
class MyClass():
pass
MyClass = type("MyClass", (), {})
다시 metaclass로 돌아가 custom metaclass에 대해 알아보자.
객체를 생성하는 코드를 보면,
class Foo:
pass
f = Foo()
Foo()를 만났을 때, Foo의 parent class의 __call__()이 불린다. super class라는걸 주의하자. Foo에서 __call__을 정의해도 불리지 않는다. 따라 올라가면, type이 나오게 되고, type에서 __call__()은 다음 두 __new__(), __init__()함수를 순서대로 호출한다. 이 두 함수는 overriding하면 그것이 호출된다.
만약 metaclass인 type을 상속받아 클래스를 만들게 되면,
class MyType(type):
def __new__(cls, name, bases, dct):
x = super().__new__(cls, name, bases, dct)
x.attr = 100
return x
metaclass를 상속받았으므로, MyType도 type의 __new__를 변형한 metaclass가 된다. 그렇다면, 이렇게 사용자가 만든 metaclass는 어떻게 사용할 수 있을까? 클래스 정의시, ‘metaclass’ 키워드를 이용한다.
class Bar(metaclass=MyType):
pass
print(Bar.attr)
100
시작은 design pattern의 singleton 구현 때문이었는데, 자료를 찾아보는 과정에서 custom metaclass가 꼭 필요한건 아니라고 느꼈다. decorator를 이용해서 비슷한 역할이 가능하기도 하고. stack overflow의 singleton 구현 답변 참조. 어쨌든, metaclass를 이용하면 꽤 깔끔하게 구현이 되는듯.