오늘부터 파이썬 공식 문서를 읽어야겠다.
파이썬에 대한 이해도가 심각하게 부족함을 느꼈다. (사실 이전부터 틈만 나면 느꼈지만…)
파이썬의
__new__와__init__의 구분은 개인적으로 파이썬에서 짜증나는 몇 안 되는 구석 중 하나이다. 정의에 따라, 기본적으로 클래스 C를 호출했을 때 실제로 호출되는 함수C.__call__의 구현은 다음과 같이 된다.def __call__(cls, *args, **kwargs): obj = cls.__new__(*args, **kwargs) if isinstance(obj, cls): obj.__init__(*args, **kwargs) return obj요컨대
__new__는 객체를 생성하지만 값을 집어 넣지는 않고, 만약 그 반환값이 원래 클래스의 인스턴스이면__init__를 한 번 더 호출해서 실제 값을 채워 넣는 것이다. 그러나 이 모델은 클래스 C에 자식 클래스 D가 존재하며 둘 다__new__를 갈아 치웠을 경우 완전히 깨져 버린다.이를테면, 다른 전혀 상관 없는 클래스 X가 존재하고
C(X(a), b)와D(a, b)를 똑같은 의미로 만들고 싶은데C(X(X(a)), b)는 그대로 남겨 뒀으면 좋겠다고 하자. (요컨대 클래스 D는 클래스 C의 특수화인데 편의를 위해 추가적인 메소드를 더 제공하는 것이다. 자동으로 canonicalization을 한다고 생각할 수도 있겠다.) 아무 생각 없이 구현하면C.__new__에서는 첫 인자가 정확히X(a)꼴이면 D의 인스턴스를 생성해서 제공하고,D.__new__에서는 첫 인자가 정확히a꼴이 아니면 C의 인스턴스를 생성해서 제공하게 된다. 그러나!C(X(a), b)를 실제로 호출하면C.__new__는 D의 인스턴스를 반환하지만 이는 C의 인스턴스이기도 하므로D.__init__가 한 번 더, 그것도 원치 않는 인자(X(a), b)로 불리게 되어 버린다. 이걸 막기 위해D.__init__에서 첫 인자를 특수 처리하면 이제 D의 생성자만으로 생성 불가능한 경우가 생겨 버린다.이걸 해결하는 “표준적인” 방법은 메타클래스를 써서
C.__metaclass__.__call__과D.__metaclass__.__call__을 완전히 오버라이드하는 것이다. 하지만 파이썬의 메타클래스는 사용이 썩 좋은 편이 아니며, 특히 이런 류의 코드가 많을 수록 클래스 하나당 새 메타클래스를 따로 만들어야 하므로 구찮아 뒈질 것 같다. 그래서 종국에는 이따위 걸 만들어 버렸다.class gentype(type): def __new__(self, name, bases, dict): if '__slots__' not in dict: dict['__slots__'] = () if '__gen__' in dict: dict['__gen__'] = staticmethod(dict['__gen__']) return type.__new__(self, name, bases, dict) def __call__(self, *args, **kwargs): obj = self.__gen__(self, *args, **kwargs) if obj is NotImplemented: obj = type.__call__(self, *args, **kwargs) return obj class genobject(object): __metaclass__ = gentype __gen__ = type.__call__이 genobject로부터 상속받는 모든 클래스는 생성자가 호출되면 모든 것에 앞서
__gen__이라는 특수 메소드를 먼저 부른다. 만약 이 메소드가NotImplemented를 반환하면 그때서야 원래대로__new__와__init__를 사용하여 새 객체를 만든다. (다만 genobject의 기본__gen__구현은 성능-_-을 생각하여 그냥type.__call__로 되어 있다.)__new__가 파이썬에서 내부적으로 staticmethod로 처리되듯,__gen__도 마찬가지로 별도로 처리하지 않아도 staticmethod가 된다.이 코드는 주어진 인자에 따라 인스턴스 생성 과정을 완전히 갈아 엎어야 할 때 편리하게 사용될 수 있을 것이다. (다만 약간 느려지는 것 같긴 하더라.) 너무 간단한 코드이니만큼 public domain으로 공개하니 알아서 잘 뜯어 고쳐 쓰시길 바란다.