반응형

원본 링크



Introduction to Python Metaclasses

 

이글에서는 메타클래스(metaclass)가 무엇이고 파이썬으로 어떻게 구현하는지 그리고 커스텀 메타클래스를 어떻게 생성하는지를 알아본다.

 

파이썬에서 메타클래스는 클래스가 어떻게 동작하는지를 정의한 클래스의 클래스이다. 클래스는 메타클래스의 인스턴스 자체이다. 파이썬에서 클래스는 클래스의 인스턴스가 어떻게 동작할지를 정의한다. 메타클래스를 잘 이해하기 위해 이전에 파이썬 클래스로 작업해본 경험이 필요하다. 메타클래스를 더 자세히 알아보기 전에 몇가지 개념을 알아보자.

파이썬의 모든 것은 객체(Object)이다.


class TestClass():
    pass

my_test_class = TestClass()
print(my_test_class)

<__main__.TestClass object at 0x7f6fcc6bf908>

 

파이썬 클래스는 동적으로 생성될 수 있다.

파이썬내 type은 객체의 타입을 알 수 있게 한다. 위에서 생성한 객체의 타입을 확인할 수 있다.


type(TestClass)
type

type(type)
type

방금 어떤일이 일어났을까? 클래스가 되기 위해 위에서 생성한 객체의 타입을 기대했지만, 아니었다. 이것에 대해서는 조금더 후에 다룰 것이니 잠시 접어두자. 여기서 또한 type 자신의 타입이 type인 것을 알 수 있다. 이것은 type의 인스턴스이다. 또 다른 마법같은 일은 type이 클래스를 동적으로 생성할 수 있게 한다는 것이다. 어떻게 하는 것인지는 아래 코드를 보자. 아래의 DataCamp 클래스는 type을 사용하여 생성된다.


class DataCamp():
    pass

DataCampClass = type('DataCamp', (), {})

print(DataCampClass)
print(DataCamp())

<class '__main__.DataCamp'>
<__main__.DataCamp object at 0x7f6fcc66e358>

위의 예에서 DataCamp는 클래스명인 반면, DataCampClass는 클래스 참조(reference)를 갖는 변수이다. type을 사용할 때 아래와 같이 딕셔너리를 사용하여 클래스 속성을 전달할 수 있다.


PythonClass = type('PythonClass', (), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass.start_date, PythonClass.instructor)
print(PythonClass)

August 2018 John Doe
<class '__main__.PythonClass'>

DataCamp 클래스로부터 상속되는 PythonClass를 원하는 경우, type을 사용하여 클래스를 정의할 때 두번째 인자로 DataCamp를 전달한다.


PythonClass = type('PythonClass', (DataCamp,), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass)

<class '__main__.PythonClass'>

파이썬은 메타클래스를 이용하여 클래스를 생성한다는 것을 알 수 있다. 파이썬의 모든 것은 객체이고 이 객체는 메타클래스로 만들어진다. 클래스를 생성하기 위해 class를 호출하는 언제라도 뒤에서 클래스를 생성하는 마술을 부리는 메타클래스가 있다. 위에서 이미 type이 이러한 동작을 하는 것을 확인했다. 이것은 String을 생성하는 str과 integer를 생성하는 int와 유사하다. 파이썬에서 __class__속성은 현재 인스턴스의 타입을 확일할 수 있도록 한다. String을 생성하고 타입을 확인해 보자.


article = 'metaclasses'
article.__class__

str

또한 type(articl)을 사용하여 타입을 확일할 수도 있다.


type(article)

str

str의 타입을 확인할 때, 이것의 타입을 알 수 있다.


type(str)

type

float, int, list, tuple, dict의 타입을 확인할 때도 유사한 출력을 갖는다. 이는 이들 객체모두의 타입이 type이기 때문이다.


print(type(list),type(float), type(dict), type(tuple))

<class 'type'> <class 'type'> <class 'type'> <class 'type'>

위에서 이미 type이 클래스를 생성하는 것을 확인했다. 따라서, __class__의 __class__를 확인하면 type이 반환된다.


article.__class__.__class__

type



커스텀 메타클래스 생성하기

파이썬에서 클래스 정의에 metaclass 키워드를 전달하여 클래스 생성 순서를 커스텀할 수 있다. 또한 이것은 metaclass 키워드로 이미 전달된 클래스를 상속하면서 완료된다.


class MyMeta(type):
    pass

class MyClass(metaclass=MyMeta):
    pass

class MySubclass(MyClass):
    pass

아래처럼 MyMeta 클래스는 type 타입이고 MyClass와 MySubClass는 MyMeta 타입이다.


print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))

<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>

클래스를 정의하면서 정의된 메타 클래스가 없으면 기본 type 메타클래스가 사용된다. 만약 메타클래스가 주어지고 이 메타클래스가 type()의 인스턴스가 아니면 주어진 메타클래스가 직접 사용된다.



__new__ 와 __init__

또한 메타클래스는 아래처럼 두가지 방법중 하나로 선언될 수 있다.


class MetaOne(type):
    def __new__(cls, name, bases, dict):
        pass

class MetaTwo(type):
    def __init__(self, name, bases, dict):
        pass

__new__는 클래스가 생성되기 전에 dict 또는 bases tuple을 정의하고 싶을때 사용된다. __new__의 반환값은 보통 변경할 수 없는 타입의 서브클래스가 인스턴스 생성을 커스텀하는 것을 허용하는 cls.__new__의 인스턴스이다. 이것은 커스텀 메타클래스에서 클래스 생성을 커스텀하기 위해 오버라이드 될 수 있다. __init__은 보통 초기화를 위해 객체가 생성된 후 호출된다.

 

Metaclass __call__ method

공식문서에 따르면, 클래스가 호출되었을 때 커스텀 종작을 허용하는 메타클래스내 커스텀 __call__ 메소드를 정의하여 다른 클래스 메소드를 오버라이드 할 수 있다.

 

Metaclass __prepare__ method

파이썬 data model docs에 따르면

적절한 메타클래스가 식별된 후 클래스 namespace가 준비된다. 만약 메타클래스가 __prepare__ 속성을 가지고 있다면, namespace = metaclass.__prepare__(name, bases, **kwds)로써 호출된다.(여기서 추가 키워드 인자는 있다면 클래스 정의로부터 온다) 만약 메타클래스가 __prepare__ 속성을 가지고 있지 않다면, 클래스 네임스페이스는 비어있는 정렬된 매핑으로 초기화된다. - docs.python.org

 

메타클래스를 이용한 싱글톤 설계

싱글톤(singleton)은 클래스의 인스턴스화를 오로지 하나의 객체로 제한하는 디자인 패턴이다. 이 패턴은 데이터베이스에 접속하기 위한 클래스를 설계할 때 같은 경우 유용할 수 있다.


class SingletonMeta(type):
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class SingletonClass(metaclass=SingletonMeta):
    pass



Conclusion

이 글에서 메타클래스가 무엇이고 파이썬으로 어떻게 구현할 수 있는지 알아보았다. 메타클래스는 로그, 생성시 클래스 등록과 다른 클래스 사이에서의 프로파일링에 적용될 수 있다. 이것은 꽤 추상적인 개념같고 이를 사용해야 하는지 궁금할 것이다. 오랜기간 파이썬 프로그래머인 Tim Peters가 그 질문에 적합한 답을 했다.

메타클래스는 99%의 사용자가 고민해야 하는 것보다 더 깊은 마술이다. 이것이 필요할지 궁금하다면, 필요없는 것이다.(실제 이것이 필요한 사람은 확실하게 그것이 필요하다는 것을 알고 왜 필요한지에 대한 설명은 필요없다.) - realpython

반응형

+ Recent posts