메타클래스에 대해 알아보기 전에 먼저 객체와 인스턴스에 대해 한번 짚고 넘어가자.
클래스로 만든 객체를 인스턴스라고 한다. 그렇다면 객체와 인스턴스의 차이는 무엇일까?
예를 들어 a = Person() 이렇게 만든 a는 객체이다. 그리고 a 객체는 Person의 인스턴스이다.
즉, 인스턴스라는 말은 특정 객체가 어떤 클래스의 객체인지를 관계 위주로 설명할 때 사용된다.
파이썬에서의 모든 것은 객체이다. 따라서 클래스도 객체이다.
그렇다면 클래스를 만들기 위한 클래스가 필요하다는 말이 되는데, 클래스를 만들기 위한 클래스는 무엇일까?
바로 이것이 메타클래스이다. 메타클래스는 우리가 재정의해주지 않으면 기본적으로 클래스를 만드는 클래스로 정해져 있다. 만약, 특정 클래스를 만들 때, 특별한 규칙을 적용하고 싶다면 메타클래스를 재정의하여 활용할 수 있다.
클래스의 동작을 내 뜻대로 제어하고 싶을 때 메타클래스를 사용할 수 있는 것이다.
그렇다면, 메타클래스는 어떻게 만들 수 있을까?🤔
type이라는 키워드를 사용하여 메타클래스를 만들 수 있다. 일반적으로 우리가 아는 type의 사용법은 type(a)와 같이 사용하여, 자료형의 종류를 알아낼 때 사용한 경험이 있을 것이다. 동일한 type이라는 키워드를 사용하여 클래스를 만들 수 있는데, 두 가지 방법으로 만들 수 있다.
먼저 type안에 [클래스 이름(string)], [상속할 부모 클래스(tuple)], [속성/메서드(dict)]를 지정하여 만들 수 있다.
>>> Hello = type('Hello', (), {})
>>> Hello
<class '__main__.Hello'>
>>> h = Hello()
>>> h
<__main__.Hello object at 0x029B4860>
두 번째 방법으로는 type을 상속받아서 메타클래스를 구현할 수 있다.
class MakeCls(type):
def __new__(metacls, name, bases, namespace):
namespace['description'] = '메타 클래스'
namespace['add'] = lambda self, a, b: a + b
return type.__new__(metacls, name, bases, namespace)
Sum = MakeCls('Sum', (), {})
s = Sum()
print(s.description) # '메타 클래스'
print(s.add(1, 2)) # 3
위와 같이 두 가지 방법으로 메타클래스를 만들 수 있다. 앞서 말했듯이 특정 클래스의 동작을 제어하고 싶을 때 메타클래스를 사용한다. 예를 들어, singleton 기능을 위한 메타클래스를 만들어보자.
class Singleton(type):
__instance = {}
def __call__(cls, *args, **kwargs):
if cls not in cls.__instance:
cls.__instance[cls] = super().__call__(*args, **kwargs)
return cls.__instance[cls]
class Test(metaclass=Singleton):
pass
a = Test()
b = Test()
print(a is b) # True
Singleton이라는 메타클래스는 클래스로 인스턴스를 만들 때 호출되는 메서드인 __call__을 오버라이드하여, 클래스로 인스턴스를 생성하였으면 만들어진 인스턴스를 반환하고, 생성하지 않았으면 인스턴스를 생성하여 __instance 속성에 저장하도록 구현되어 있다.
이렇게 만든 Singleton을 상속받아 Test 클래스를 만들고, Test 클래스로 만들어진 인스턴스를 비교해 보면 모두 같은 인스턴스임을 확인할 수 있다. 여기서 메타클래스로 만들어진 자식 클래스를 생성할 때, 호출 순서를 확인해보면
metaclass __new__
metaclass __init__
====================================
metaclass __call__
child __init__
위와 같은 순서로, 자식클래스가 생성되기도 전에 메타클래스가 생성되고 초기화된 것을 확인할 수 있다.
class Test(metaclass=Singleton) 이라고 클래스가 로드되는 순간, Test라는 객체가 생성된 것이고, 객체가 생성되었다는 것은 클래스의 생성자가 호출되었다는 말이다. Test 클래스의 클래스는 Singleton이고, Singleton의 생성자가 호출된다는 것을 알 수 있으며, Test 클래스는 이미 Singleton 클래스의 인스턴스이다.
개발을 하며 메타클래스를 사용할 일이 많이 없을 수도 있지만, 파이썬의 구조에 대해 더욱 자세히 이해할 수 있는 글이 되었으면 한다.
참고 : https://wikidocs.net/21056, https://alphahackerhan.tistory.com/34
'🐍Python | Django' 카테고리의 다른 글
[Django] Django MTV (0) | 2022.04.29 |
---|---|
[Python] call by value? call by reference? (0) | 2022.04.28 |
[Python] main 함수 (0) | 2022.04.26 |
[Python] Class와 상속(Inheritance) (0) | 2022.04.19 |
[Python] Decorator (0) | 2022.04.18 |