복붙노트

[PYTHON] __new__ 대신 metaclass의 __call__ 메소드를 사용합니까?

PYTHON

__new__ 대신 metaclass의 __call__ 메소드를 사용합니까?

메타 클래스에 관해 논의 할 때 문서의 내용은 다음과 같습니다.

내 질문은 : 클래스를 호출 할 때 사용자 지정 동작 (예 : 신선한 개체를 만드는 대신 캐싱)을 사용한다고 가정합니다. 클래스의 __new__ 메서드를 재정 의하여이 작업을 수행 할 수 있습니다. 대신 __call__을 사용하여 메타 클래스를 정의하고 싶습니다. 이 접근 방식은 __new__에서 달성 할 수없는 것이 무엇입니까?

해결법

  1. ==============================

    1.질문에 대한 직접적인 대답은 인스턴스 작성을 사용자 정의하는 것 이상의 작업을 수행하려는 경우 또는 클래스 작성 방법과 인스턴스 작성 방법을 분리하려는 경우입니다.

    질문에 대한 직접적인 대답은 인스턴스 작성을 사용자 정의하는 것 이상의 작업을 수행하려는 경우 또는 클래스 작성 방법과 인스턴스 작성 방법을 분리하려는 경우입니다.

    파이썬에서 싱글 톤 만들기와 관련 토론에 대한 제 대답을보십시오.

    몇 가지 장점이 있습니다.

    단일 책임 원리에 대해 걱정하지 않는다면 __new__ 커스터마이징이 잘 작동하는 경우가 많이 있습니다.

    그러나 인스턴스가 생성 될 때가 아니라 클래스가 생성 될 때 이전에 발생해야하는 다른 유스 케이스가 있습니다. 메타 클라스 (metaclass)가 필요하다는 것이 이들이 등장 할 때입니다. 파이썬에서 메타 실용 (구체적) 사용 사례는 무엇입니까?를보십시오. 훌륭한 예제가 많이 있습니다.

  2. ==============================

    2.한가지 차이점은 metaclass __call__ 메쏘드를 정의하면 클래스 나 서브 클래스의 __new__ 메쏘드 중 어떤 것이 호출 될 수 있기 전에 그것이 호출되도록 요구한다는 것입니다.

    한가지 차이점은 metaclass __call__ 메쏘드를 정의하면 클래스 나 서브 클래스의 __new__ 메쏘드 중 어떤 것이 호출 될 수 있기 전에 그것이 호출되도록 요구한다는 것입니다.

    class MetaFoo(type):
        def __call__(cls,*args,**kwargs):
            print('MetaFoo: {c},{a},{k}'.format(c=cls,a=args,k=kwargs))
    
    class Foo(object):
        __metaclass__=MetaFoo
    
    class SubFoo(Foo):
        def __new__(self,*args,**kwargs):
            # This never gets called
            print('Foo.__new__: {a},{k}'.format(a=args,k=kwargs))
    
     sub=SubFoo()
     foo=Foo()
    
     # MetaFoo: <class '__main__.SubFoo'>, (),{}
     # MetaFoo: <class '__main__.Foo'>, (),{}
    

    SubFoo .__ new__는 결코 호출되지 않습니다. 반대로 메타 클래스없이 Foo .__ new__를 정의하면 하위 클래스가 Foo .__ new__를 재정의 할 수 있습니다.

    물론, 당신은 MetaFoo .__ call__을 정의하여 cls .__ new__를 호출 할 수 있습니다.하지만 그건 당신에게 달렸습니다. 이를 거부하면 하위 클래스에서 __new__ 메서드를 호출하지 못하도록 방지 할 수 있습니다.

    나는 여기서 메타 클래스를 사용하는 것에 대한 강력한 이점을 보지 못했다. 그리고 "Simple은 복잡한 것보다 낫다"때문에 __new__을 사용하는 것이 좋습니다.

  3. ==============================

    3.미묘한 차이점은 이러한 메소드의 실행 순서를 신중하게 관찰 할 때 조금 더 눈에 띄게됩니다.

    미묘한 차이점은 이러한 메소드의 실행 순서를 신중하게 관찰 할 때 조금 더 눈에 띄게됩니다.

    class Meta_1(type):
        def __call__(cls, *a, **kw):
            print "entering Meta_1.__call__()"
            rv = super(Meta_1, cls).__call__(*a, **kw)
            print "exiting Meta_1.__call__()"
            return rv
    
    class Class_1(object):
        __metaclass__ = Meta_1
        def __new__(cls, *a, **kw):
            print "entering Class_1.__new__()"
            rv = super(Class_1, cls).__new__(cls, *a, **kw)
            print "exiting Class_1.__new__()"
            return rv
    
        def __init__(self, *a, **kw):
            print "executing Class_1.__init__()"
            super(Class_1,self).__init__(*a, **kw)
    

    위의 코드는 실제로 우리가하는 일을 로그하는 것 외에는 아무 것도하지 않습니다. 각 메소드는 상위 구현, 즉 기본값으로 연기됩니다. 따라서 로깅 옆에 단순히 다음과 같이 선언 한 것처럼 효과적입니다.

    class Meta_1(type): pass
    class Class_1(object):
        __metaclass__ = Meta_1
    

    이제 Class_1 인스턴스를 만듭니다.

    c = Class_1()
    # entering Meta_1.__call__()
    # entering Class_1.__new__()
    # exiting Class_1.__new__()
    # executing Class_1.__init__()
    # exiting Meta_1.__call__()
    

    그러므로 type이 Meta_1의 부모라면 우리는 .__ call __ ()과 같은 유형의 의사 구현을 상상할 수 있습니다 :

    class type:
        def __call__(cls, *args, **kwarg):
    
            # ... a few things could possibly be done to cls here... maybe... or maybe not...
    
            # then we call cls.__new__() to get a new object
            obj = cls.__new__(cls, *args, **kwargs)
    
            # ... a few things done to obj here... maybe... or not...
    
            # then we call obj.__init__()
            obj.__init__(*args, **kwargs)
    
            # ... maybe a few more things done to obj here
    
            # then we return obj
            return obj
    

    Meta_1 .__ call __ () (또는이 경우에는 .__ call __ ())을 입력하면 Class_1 .__ new __ () 및 Class_1 .__ init __ ()에 대한 호출이 최종적으로 수행되는지 여부에 영향을 줄 수 있습니다. Meta_1 .__ call __ ()을 실행하는 동안 어느 쪽이든 손도 닿지 않은 객체를 반환 할 수 있습니다. 싱글 톤 패턴에 대한이 접근법을 예로 들어 보겠습니다.

    class Meta_2(type):
        __Class_2_singleton__ = None
        def __call__(cls, *a, **kw):
            # if the singleton isn't present, create and register it
            if not Meta_2.__Class_2_singleton__:
                print "entering Meta_2.__call__()"
                Meta_2.__Class_2_singleton__ = super(Meta_2, cls).__call__(*a, **kw)
                print "exiting Meta_2.__call__()"
            else:
                print ("Class_2 singleton returning from Meta_2.__call__(), "
                        "super(Meta_2, cls).__call__() skipped")
            # return singleton instance
            return Meta_2.__Class_2_singleton__
    
    class Class_2(object):
        __metaclass__ = Meta_2
        def __new__(cls, *a, **kw):
            print "entering Class_2.__new__()"
            rv = super(Class_2, cls).__new__(cls, *a, **kw)
            print "exiting Class_2.__new__()"
            return rv
    
        def __init__(self, *a, **kw):
            print "executing Class_2.__init__()"
            super(Class_2, self).__init__(*a, **kw)
    

    Class_2 유형의 객체를 반복적으로 만들려고 할 때 어떤 일이 일어나는지 관찰 해 봅시다.

    a = Class_2()
    # entering Meta_2.__call__()
    # entering Class_2.__new__()
    # exiting Class_2.__new__()
    # executing Class_2.__init__()
    # exiting Meta_2.__call__()
    
    b = Class_2()
    # Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped
    
    c = Class_2()
    # Class_2 singleton returning from Meta_2.__call__(), super(Meta_2, cls).__call__() skipped
    
    print a is b is c
    True
    

    이제 같은 구현을 시도하기 위해 클래스의 __new __ () 메소드를 사용하여이 구현을 관찰하십시오.

    import random
    class Class_3(object):
    
        __Class_3_singleton__ = None
    
        def __new__(cls, *a, **kw):
            # if singleton not present create and save it
            if not Class_3.__Class_3_singleton__:
                print "entering Class_3.__new__()"
                Class_3.__Class_3_singleton__ = rv = super(Class_3, cls).__new__(cls, *a, **kw)
                rv.random1 = random.random()
                rv.random2 = random.random()
                print "exiting Class_3.__new__()"
            else:
                print ("Class_3 singleton returning from Class_3.__new__(), "
                       "super(Class_3, cls).__new__() skipped")
    
            return Class_3.__Class_3_singleton__ 
    
        def __init__(self, *a, **kw):
            print "executing Class_3.__init__()"
            print "random1 is still {random1}".format(random1=self.random1)
            # unfortunately if self.__init__() has some property altering actions
            # they will affect our singleton each time we try to create an instance 
            self.random2 = random.random()
            print "random2 is now {random2}".format(random2=self.random2)
            super(Class_3, self).__init__(*a, **kw)
    

    위의 구현은 클래스에 싱글 톤을 성공적으로 등록하더라도 __init __ ()가 호출되는 것을 막지는 못하지만, __init __ () 유형 (암시가없는 경우 기본 메타 클래스 임)에서 암시 적으로 발생합니다. 이로 인해 원하지 않는 결과가 발생할 수 있습니다.

    a = Class_3()
    # entering Class_3.__new__()
    # exiting Class_3.__new__()
    # executing Class_3.__init__()
    # random1 is still 0.282724600824
    # random2 is now 0.739298365475
    
    b = Class_3()
    # Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
    # executing Class_3.__init__()
    # random1 is still 0.282724600824
    # random2 is now 0.247361634396
    
    c = Class_3()
    # Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
    # executing Class_3.__init__()
    # random1 is still 0.282724600824
    # random2 is now 0.436144427555
    
    d = Class_3()
    # Class_3 singleton returning from Class_3.__new__(), super(Class_3, cls).__new__() skipped
    # executing Class_3.__init__()
    # random1 is still 0.282724600824
    # random2 is now 0.167298405242
    
    print a is b is c is d
    # True
    
  4. ==============================

    4.라이프 사이클 단계의 문제이며 액세스 권한이 있습니다. __call__은 __new__ 뒤에 호출되고 초기화 매개 변수가 전달되어 __init__에 전달되기 전에 조작 매개 변수를 조작 할 수 있습니다. 이 코드를 시험해보고 그 결과를 연구하십시오 :

    라이프 사이클 단계의 문제이며 액세스 권한이 있습니다. __call__은 __new__ 뒤에 호출되고 초기화 매개 변수가 전달되어 __init__에 전달되기 전에 조작 매개 변수를 조작 할 수 있습니다. 이 코드를 시험해보고 그 결과를 연구하십시오 :

    class Meta(type):
        def __new__(cls, name, bases, newattrs):
            print "new: %r %r %r %r" % (cls, name, bases, newattrs,)
            return super(Meta, cls).__new__(cls, name, bases, newattrs)
    
        def __call__(self, *args, **kw):
            print "call: %r %r %r" % (self, args, kw)
            return super(Meta, self).__call__(*args, **kw)
    
    class Foo:
        __metaclass__ = Meta
    
        def __init__(self, *args, **kw):
            print "init: %r %r %r" % (self, args, kw)
    
    f = Foo('bar')
    print "main: %r" % f
    
  5. ==============================

    5.pyroscope의 대답에 대한 파이썬 3 버전이 복사, 붙여 넣기 및 해킹 (누군가가이 페이지에서 다시 6 개월 만에 다시 돌아올 때 나를 만났을 때)으로 편리 할 수 ​​있다고 생각했습니다. 이 기사에서 가져온 것입니다 :

    pyroscope의 대답에 대한 파이썬 3 버전이 복사, 붙여 넣기 및 해킹 (누군가가이 페이지에서 다시 6 개월 만에 다시 돌아올 때 나를 만났을 때)으로 편리 할 수 ​​있다고 생각했습니다. 이 기사에서 가져온 것입니다 :

    class Meta(type):
    
         @classmethod
         def __prepare__(mcs, name, bases, **kwargs):
             print('  Meta.__prepare__(mcs=%s, name=%r, bases=%s, **%s)' % (
                 mcs, name, bases, kwargs
             ))
             return {}
    
         def __new__(mcs, name, bases, attrs, **kwargs):
             print('  Meta.__new__(mcs=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
                 mcs, name, bases, ', '.join(attrs), kwargs
             ))
             return super().__new__(mcs, name, bases, attrs)
    
         def __init__(cls, name, bases, attrs, **kwargs):
             print('  Meta.__init__(cls=%s, name=%r, bases=%s, attrs=[%s], **%s)' % (
                 cls, name, bases, ', '.join(attrs), kwargs
             ))
             super().__init__(name, bases, attrs)
    
         def __call__(cls, *args, **kwargs):
             print('  Meta.__call__(cls=%s, args=%s, kwargs=%s)' % (
                 cls, args, kwargs
             ))
             return super().__call__(*args, **kwargs)
    
    print('** Meta class declared')
    
    class Class(metaclass=Meta, extra=1):
    
         def __new__(cls, myarg):
             print('  Class.__new__(cls=%s, myarg=%s)' % (
                 cls, myarg
             ))
             return super().__new__(cls)
    
         def __init__(self, myarg):
             print('  Class.__init__(self=%s, myarg=%s)' % (
                 self, myarg
             ))
             self.myarg = myarg
             super().__init__()
    
         def __str__(self):
             return "<instance of Class; myargs=%s>" % (
                 getattr(self, 'myarg', 'MISSING'),
             )
    
    print('** Class declared')
    
    Class(1)
    print('** Class instantiated')
    

    출력 :

    ** Meta class declared
      Meta.__prepare__(mcs=<class '__main__.Meta'>, name='Class', bases=(), **{'extra': 1})
      Meta.__new__(mcs=<class '__main__.Meta'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
      Meta.__init__(cls=<class '__main__.Class'>, name='Class', bases=(), attrs=[__module__, __qualname__, __new__, __init__, __str__, __classcell__], **{'extra': 1})
    ** Class declared
      Meta.__call__(cls=<class '__main__.Class'>, args=(1,), kwargs={})
      Class.__new__(cls=<class '__main__.Class'>, myarg=1)
      Class.__init__(self=<instance of Class; myargs=MISSING>, myarg=1)
    ** Class instantiated
    

    같은 기사에서 강조된 또 다른 훌륭한 자료는 David Beazley의 PyCon 2013 Python 3 Metaprogramming 튜토리얼입니다.

  6. from https://stackoverflow.com/questions/6966772/using-the-call-method-of-a-metaclass-instead-of-new by cc-by-sa and MIT license