반응형

Python metaclass는 클래스 자체가 만들어지고 호출되는 방식을 바꾸는 고급 기능이다. Singleton 구현처럼 인스턴스 생성 시점의 동작을 가로채야 할 때 metaclass의 __call__ 흐름을 이해해야 한다.

이 글은 Python 2 스타일 __metaclass__ 예제를 바탕으로 SingletonMixin이 왜 일반 mixin이 아니라 metaclass 동작을 이용하는지 추적한 디버깅 메모다.

 

핵심 정리

Python에서 보통 객체를 만들면 클래스가 호출되고, 그 과정에서 인스턴스 생성 로직이 실행된다. 그런데 클래스도 객체이므로, 그 클래스를 만든 metaclass가 클래스 호출 동작에 개입할 수 있다. 원문 예제의 Singleton은 type을 상속한 metaclass이고, __call__을 오버라이드해 인스턴스가 이미 있으면 기존 객체를 돌려준다. SingletonMixin에 __metaclass__가 지정되어 있으면 해당 클래스를 호출할 때 일반 클래스의 생성 흐름만 보는 것이 아니라 metaclass의 __call__이 먼저 관여한다. 그래서 LiveStrategyManager처럼 좌변에 대입하지 않고 호출만 해도 인스턴스 생성 시도가 발생하며, 그 순간 Singleton의 __call__이 실행된다. 최신 Python 코드에서는 metaclass 지정 문법이 달라질 수 있으므로 원문은 Python 2 문맥의 동작 이해용으로 보는 것이 좋다.

  • metaclass는 클래스를 만드는 클래스라고 볼 수 있다.
  • type을 상속하면 사용자 정의 metaclass를 만들 수 있다.
  • metaclass의 __call__은 클래스가 호출되어 인스턴스를 만들 때 개입한다.
  • Singleton 패턴은 __call__에서 기존 인스턴스를 재사용하는 방식으로 구현할 수 있다.
  • 원문 예제의 __metaclass__ 지정은 Python 2 스타일 문법이다.
  • 클래스를 호출하는 표현만으로도 인스턴스 생성 흐름이 시작된다.
  • metaclass는 강력하지만 일반 상속이나 decorator보다 이해 비용이 높다.
  • 단순 기능 추가라면 metaclass보다 함수, decorator, composition을 먼저 검토하는 편이 좋다.

원문은 SingletonMixin이 실제로는 metaclass 흐름을 타고 있었다는 사실을 디버깅하며 정리한 글입니다. 보강문에서는 클래스 호출, metaclass __call__, Singleton 구현이 어떻게 이어지는지 먼저 설명했습니다. decorator라는 제목 요소는 넓은 Python 동작 원리 맥락에 남아 있지만, 이 글의 핵심은 metaclass를 통한 인스턴스 생성 제어입니다.

이어서 볼 글

 

metaclass

딱걸렸다. SingletonMixin은 metaclass였던것이다.

http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/#python-2-metaclass


import threading

class Singleton(type):
    def __init__(cls, name, bases, dict):
        super(Singleton, cls).__init__(name, bases, dict)
        cls._instance = None
        cls._lock = threading.Lock()

    def __call__(cls, *args, **kwargs):
        with cls._lock:
            if cls._instance is None:
                cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
            return cls._instance

class SingletonMixin(object):
    __metaclass__ = Singleton

이거 재밌는 개념이네.. 아래 코드를 보자.


class Meta(type):
    pass

class Complex1(object):
    pass

class Complex2(Meta):
    pass

class Complex3(object):
    __metaclass__ = Meta

print type(Complex1)
print type(Complex2)
print type(Complex3)

결과
<type 'type'>
<type 'type'>
<class '__main__.Meta'>

전부 type이나 object로 부터 상속받고 있으므로 new-style class들이고 (따라서 metaclass(아마도) 및 super()가 사용 가능)

원래는 type()을 하면은 <type 'type'>이렇게만 나오는데

__metaclass__ 선언을 한순간 type이 바뀜을 알 수 있다.

자, 내가 궁금했던데 아래 코드를 부르는 순간



 def _start_strategies(self):
     self.info('Initializing strategies')

     for strategy in self._strategies:
         import pdb; pdb.set_trace()  # XXX BREAKPOINT
         LiveStrategyManager().start(strategy, self,
                                     paused=not settings.DEBUG)

왜 아래처럼 부모 클래스인 Singleton의 __call__이 불리냐 했던건데



 class Singleton(type):
     def __init__(cls, name, bases, dict):
         super(Singleton, cls).__init__(name, bases, dict)
         cls._instance = None
         cls._lock = threading.Lock()

     def __call__(cls, *args, **kwargs):
         with cls._lock:
             if cls._instance is None:
                 cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
             return cls._instance

 class SingletonMixin(object):
     __metaclass__ = Singleton

알고보니 metaclass로 선언을 해 두어서, 인스턴스를 만드는 순간

(obj = LiveStrategyManager() 를 하지 않았지만

LiveStrategyManger()만 해도 좌변만 없다 뿐이지 instance creation 같다.)

아래처럼 metaclass의 __call__을 먼저 호출해주었던 거시다!

http://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/

이런속성이 있으니까 여기(__call__)에다가 싱글톤을 구현하기 딱 좋았던 것..

궁금증 해결 끝!

반응형

+ Recent posts