[Python] 메타클래스(MetaClass)

cdwde·2021년 5월 22일
0

🎈 메타클래스란?

  • 클래스를 만드는 '무언가'
  • 클래스의 클래스

🎈 메타클래스 구현하는 방법

type을 사용하여 동적으로 클래스를 생성하는 방식

  • type은 python이 실제로 보이는 코드 뒤에서 클래스를 생성하는 메타클래스

  • 클래스 = type('클래스이름', '기반클래스튜플', '속성메서드딕셔너리')

  • 인자로 넘긴 클래스이름이 클래스의 이름으로 사용되고
    이를 할당한 변수는 클래스의 레퍼런스를 갖고 있음

class MyClass(object):
    pass
MyClass = type('MyClass', (), {})

print(MyClass)
print(MyClass())	#클래스 인스턴스 생성

#출력 결과
#<class '__main__.MyClass'>
#<__main__.MyClass object at 0x01E9F430>

  • type은 클래스의 속성을 정의하기 위해 dict를 인자로 받음
class MyClass(object):
    bar = True
MyClass = type('MyClass', (), {'bar' : True}

  • type은 기반 클래스를 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
  • 메서드가 간단하다면 lambda 표현식 사용해도 됨
    (메서드 첫번째 매개변수는 반드시 self여야하기 때문에 람다 표현식에도 self 지정해줘야 함)

type을 상속받아 메타클래스를 구현하는 방식

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__

  1. __new__ : 클래스 인스턴스를 생성(메모리 할당)
  2. __init__: 생성된 인스턴스 초기화
  3. __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

0개의 댓글