Home metaclass - 2
Post
Cancel

metaclass - 2

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

Refercence

This post is licensed under CC BY 4.0 by the author.

metaclass

Gihub issue, pr template 등록하기