본문으로 바로가기

Python Metaclass 사용해보기

category Coding/Python 2022. 7. 6. 19:10
반응형

개요

파이썬에는 메타 클래스(Metaclass)라는 것이 존재한다. 메타 프로그래밍(Meta prgramming)이라고도 불리는데 이를 이용하면 특이한 행위를 수행하는 클래스를 만들 수 있다. 기본적으로 파이썬에서 메타 클래스란 클래스를 만드는 클래스를 의미하고 이를 구현하는 방법은 아래와 같이 두 가지가 존재한다.

- type을 사용하여 동적으로 클래스를 생성하는 방식
- type을 상속받은 후 메타 클래스를 구현하는 방식

대부분의 경우에서 메타 클래스가 필요한 경우는 없다고 볼 수 있다. 인터넷을 찾아보면 Django ORM 등을 구현할 때 사용하는 방법이라는 예제도 나와있는데 실제로 나도 실무에서 메타 클래스를 사용한 경험은 거의 없었다.
구글 등 파이썬 메타 클래스에 대해 검색하면 수많은 자료들이 나오므로 본 포스팅에서는 메타 클래스에 대해 자세히 기술하진 않는다. 다만, 메타 클래스를 사용하여 조금 특이한 행위를 수행해볼 것이다. 도움이 될 수도, 되지 않을 수도 있지만 간단한 예를 통해 개인적인 경험을 풀어본다.

Usage

이전에 fastapi-event(https://github.com/teamhide/fastapi-event) 라는 오픈 소스 라이브러리를 만들었었다. (관련 내용은 https://www.hides.kr/1091 참고) 해당 라이브러리는 FastAPI에서 이벤트를 실행하는 라이브러리인데 동작에 필요한 핵심 클래스인 EventHandler라는 클래스가 있었다.

class EventHandler:
    def __init__(self, validator: EventHandlerValidator):
        self.events: Dict[Type[BaseEvent], Union[BaseModel, None]] = {}
        self.validator = validator

    async def store(self, event: Type[BaseEvent], parameter: BaseModel = None) -> None:
        await self.validator.validate(event=event, parameter=parameter)
        self.events[event] = parameter
    
    ...

다른 부분은 크게 살펴볼 필요없이 핵심만 말하자면 store() 메소드를 통해 이벤트를 내부 딕셔너리에 저장하는 행동을 수행한다. 사용자가 해당 클래스를 사용하게 만들기 위해 다음과 같이 2가지 방법이 존재한다.

from fastapi_event import get_event_handler


handler = get_event_handler()

첫 번째 방법은 클래스를 인스턴스화 시키는 함수를 제공하고 유저가 직접 인스턴스를 생성하는 방식이다.

from fastapi_event import EventHandler


handler = EventHandler()

두 번째 방법은 위처럼 클래스 자체를 제공하고 유저가 직접 인스턴스화하는 방식이다.
하지만 나는 위 2가지 방법 모두 사용하고 싶지 않았다. 라이브러리 내부에서 명시적인 객체 생성도 피하고 싶었다. 아래처럼 단순히 import후 바로 사용하는 방식을 원했다.

from fastapi_event import event_handler


event_handler.store()

이 때 메타 클래스를 이용할 수 있다.

class Handler(type):
   def publish(self):
      print("Meta publish()")


class HandlerDelegator(metaclass=Handler):
   pass


handler = HandlerDelegator
handler.publish()

위처럼 type을 상속받은 메타클래스를 하나 생성하고 해당 클래스를 타 클래스의 metaclass속성에 넣어주면 된다. 그렇다면 우리가 직접 인스턴스화를 해주지 않아도 메타 클래스의 메소드에 접근할 수 있다. 어떻게 이런일이 가능할까? 아래의 코드를 살펴보자.

class Meta(type):
    ...


class Handler(metaclass=Meta):
    ...

class Handler()가 입력됐을 당시에는 Handler가 아직 메모리에 생성되지 않은 상태이다. 이후 클래스 정의에 __metaclass__ 또는 metaclass 속성을 살펴보고 만약 존재한다면 해당 속성에 입력된 클래스를 사용하여 Handler 객체를 생성한다. 위 코드로 살펴보면 Meta 클래스를 사용하여 Handler 클래스를 생성한다고 생각하면 된다. 물론 metaclass 속성이 비어있다면 type을 이용하여 클래스를 생성한다.
사실 조금 과장된 예제라고 생각할수도 있겠다. 다만, 본 포스팅의 주제에 맞게 메타 클래스를 활용하는 방법 중 하나를 소개했다고 생각하면 될 것 같다. 실제로 오픈소스로 배포한 fastapi-event 라이브러리에서 해당 패턴을 사용하고 있다. (https://github.com/teamhide/fastapi-event/blob/01314fa57862a500b8a887957eaa47b8ec850929/fastapi_event/handler.py#L137)

Reference

조금 더 자세한 내용을 원한다면 https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python 를 참고하자.

반응형