Python의 MetaClass에 대한 글 - 2
개요
지난 글에서는 모든 클래스의 클래스는 metaclass이다라는 의미에 대해서 간단히 알아보았다. 해당 글에서는 metaclass인 type의 사용법과 어떻게 활용될 수 있는지에 대해 예제와 함께 설명한다.
class키워드 없이 class 만들기 - 1
지난 글에서 모든 클래스는 type 클래스로 부터 만들어진것을 확인할 수 있었다.
실제로 type 클래스의 생성자를 통해서 원하는 클래스를 만들수 있다.
type class의 생성자는 다음과 같다. type(name, bases, dict)
1 - python docs에 사용법이 나와 있다.
name은 원하는 class 이름, bases는 상속 받을 class, dict는 class의 속성, 메소드를 넣어준다.
1
2
3
4
5
6
>>> MyClass = type("MyClass",(),{}) # type 생성자를 통해서 MyClass 클래스를 만들고
>>> print(MyClass)
<class '__main__.MyClass'>
>>> mc = MyClass() # MyClass 클래스의 인스턴스를 생성
>>> print(mc)
<__main__.MyClass object at 0x100638d90>
위의 예시코드를 통해서 아래 두 코드가 동치임을 알 수 있다.
1
2
class MyClass:
pass
1
MyClass = type("MyClass",(),{})
class키워드 없이 class 만들기 - 2
조금 더 복잡한 클래스를 type 생성자를 통해서 만들어보자.
1
2
3
4
class MyChildClass(MyClass):
attr1 = 1
def attr2(self, x):
return x+1
1
2
3
4
5
6
7
8
MyChildClass = type(
"MyChildClass",
(MyClass, ),
{
'attr1':1,
'attr2': lambda self,x:x+1
}
)
custom metaclass 만들기
위의 예제에서 metaclass인 type을 통해서 class를 만들었다.
metaclass인 type을 상속받아서 나만의 metaclass를 만들 수 있다.
type을 상속 받고 new, init, call 함수 overriding을 통해서 커스텀하게 metaclass를 만들 수 있다.
아래 예제 코드 및 실행결과를 살펴보며 좀 더 이해해보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyMetaClass(type):
def __new__(mcs, *args, **kwargs):
print("__new__")
print(mcs, args, kwargs)
return super().__new__(mcs, *args, **kwargs) # metaclass로 class 인스턴스 객체할당
def __init__(cls, *args, **kwargs):
print("__init__")
print(cls, args, kwargs)
super().__init__(*args, **kwargs) # class 인스턴스 초기화
def __call__(cls, *args, **kwargs):
print("__call__")
print(cls, args, kwargs)
return super().__call__(*args, **kwargs) # class 인스턴스의 __call__ 오버라이딩
1
2
3
4
5
6
7
8
9
>>> Test = MyMetaClass("Test",(),{}) # MyMetaClass이라는 custom metaclass로 Test라는 class 생성
__new__
<class '__main__.MyMetaClass'> ('Test', (), {}) {} # MyMetaClass의 인스턴스가 만들어지기때문에 __new__, __init__ 호출
__init__
<class '__main__.Test'> ('Test', (), {}) {}
>>> test = Test() # MyMetaClass의 인스턴스인 Test로 ()를 호출 했으므로 __call__ 호출
__call__
<class '__main__.Test'> () {}
위의 결과를 보면 metaclass로 Test class를 생성해 낼때 new, init 이 호출된다.
이는 metaclass로 class를 생성해 낼 때 class 관련된 로직을 넣을 수 있다는 의미이다.
또한,
Test class로 Test class의 인스턴스가 생성될 때 call 이라는 메소드가 호출 되는 것을 확인 할 수 있다.
이는 custom metaclass를 통해서 만든 class로 인스턴스를 생성할 때 원하는 로직을 넣을 수 있다.
custom metaclass 활용 예시 - Singleton
1
2
3
4
5
6
7
8
9
10
11
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class MySingletonClass(metaclass=SingletonMeta): # MySingletonClass를 만들때 type metaclass가 아닌 SingletonMeta metaclass 사용
def __init__(self, name):
self.name = name
1
2
3
4
5
6
7
8
9
10
11
>>> a = MySingletonClass("Instance A") # SingletoneMeta의 __call__ 호출 (Singleton 로직)
>>> b = MySingletonClass("Instance B") # SingletoneMeta의 __call__ 호출 (Singleton 로직)
>>> print(a.name)
Instance A
>>> print(b.name)
Instance A
>>> print(a is b)
True