type
은 python이 실제로 보이는 코드 뒤에서 클래스를 생성하는 메타클래스
클래스 = type('클래스이름', '기반클래스튜플', '속성메서드딕셔너리')
인자로 넘긴 클래스이름이 클래스의 이름으로 사용되고
이를 할당한 변수는 클래스의 레퍼런스를 갖고 있음
class MyClass(object):
pass
MyClass = type('MyClass', (), {})
print(MyClass)
print(MyClass()) #클래스 인스턴스 생성
#출력 결과
#<class '__main__.MyClass'>
#<__main__.MyClass object at 0x01E9F430>
dict
를 인자로 받음class MyClass(object):
bar = True
MyClass = type('MyClass', (), {'bar' : True}
tuple
인자로 받음class MyClassChild(MyClass):
pass
MyClassChild = type('MyClassChild', (MyClass, ), {})
Foo = type('Foo', (), {'bar': True})
def echo_bar(self):
print(self.bar)
FooChild = type('FooChild', (Foo, ), {'echo_bar': echo_bar})
print(hasattr(Foo, 'echo_bar'))
my_foo = FooChild()
my_foo.echo_bar()
#출력 결과
#False
#True
self
여야하기 때문에 람다 표현식에도 self
지정해줘야 함)class 메타클래스이름(type):
def __new__(metacls, name, bases, namespace):
pass
__new__
메서드에서 새로 만들어질 클래스에 속성과 메서드 추가할 수 있음예시
class MakeCalc(type): #type 상속 받음
def __new__(metacls, name, bases, namespace):
namespace['desc'] = '계산클래스' #새 클래스에 속성 추가
namespace['add'] = lambda self, a, b: a + b #새 클래스에 메소드 추가
return type.__new__(metacls, name, bases, namespace)
Calc = MakeCalc('Clac', (), {}) #메타클래스 MakeCalc로 클래스 Calc 생성
c = Calc()
print(c.desc) #인스턴스 c의 속성 출력
print(c.add(1, 2)) #인스턴스 c의 메소드 출력
#출력 결과
#계산클래스
#3
예시
Singleton이라는 메타클래스 통해 클래스의 인스턴스가 오직 하나만 만들어지게 제어하고 싶음
class Singleton(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 Hello(metaclass=Singleton):
pass
a = Hello() #클래스 Hello로 인스턴스 a 생성
b = Hello() #클래스 Hello로 인스턴스 b 생성
print(a is b)
#출력 결과
#True
메타클래스 Singleton이 클래스 Hello의 동작을 제어할 수 있음
보통 __call__
메소드는 인스턴스를 ()로 호출할 때 호출됨
하지만 type을 상속받은 메타클래스에서 __call__
메소드를 구현하면 메타클래스를 사용하는 클래스로 인스턴스를 만들 때 호출됨
(클래스를 ()로 호출할 때)
__new__
: 클래스 인스턴스를 생성(메모리 할당)__init__
: 생성된 인스턴스 초기화__call__
: 인스턴스 실행메타클래스는 (메타클래스를 상속한)클래스가 생성될 때 __call__
호출됨
why? => 메타클래스의 생성은 (메타클래스를 상속한) 클래스가 생성될 때 이루어지는 것 아니기 때문
class MyMetaClass(type):
def __new__(cls, *args, **kwargs):
print('metaclass__new__')
return super().__new__(cls, *args, **kwargs)
def __init__(cls, *args, **kwargs):
print('metaclass__init__')
return super().__init__(*args, **kwargs)
def __call__(cls, *args, **kwargs):
print('metaclass__call__')
return super().__call__(*args, **kwargs)
class MyClass(metaclass = MyMetaClass):
def __init__(self):
print('child__init__')
def __call__(self):
print('child__call__')
print('-------------------')
obj = MyClass()
#출력 결과
#metaclass__new__
#metaclass__init__
#-------------------
#metaclass__call__
#child__init__
MyClass
의 인스턴스가 생성되기도 전에 MyMetaClass
(MyClass의 메타클래스)가 생성되고 초기화까지 됨
class MyClass(metaclass=MyMetaClass)
라고 클래스가 로드되는 순간 MyClass라는 클래스가 만들어진 것
= MyClass라는 객체 생성된 것
= 객체가 생성되었다는 것은 클래스의 생성자가 호출되었다는 것
= MyClass 클래스의 클래스는 MyMetaClass이므로 MyMetaClass의 생성자가 호출되었다는 것
class MyClass(metaclass=MyMetaClass)
라는 클래스는 이미 MyMetaClass의 인스턴스
=> MyClass라는 클래스의 인스턴스를 생성하려고 시도하면 MyMetaClass의 인스턴스를 실행하는 것
즉 obj == MyClass() == (MyMetaClass())()
참고
https://dojang.io/mod/page/view.php?id=2468
https://tech.ssut.me/understanding-python-metaclasses/
https://alphahackerhan.tistory.com/34