python 파이썬 - __init __()가 __new __()뒤에 항상 호출되는 이유는 무엇입니까?




get set (15)

난 그냥 내 수업 중 하나를 간소화하고 플라이급 디자인 패턴 과 같은 스타일로 몇 가지 기능을 도입했습니다.

그러나, 나는 __init__ 이 항상 __new__ 다음에 호출되는 이유에 대해 다소 혼란스러워합니다. 나는 이것을 기대하지 않았다. 아무도 왜 이런 일이 일어나고 어떻게 다른 기능을 구현할 수 있는지 말해 줄 수 있습니까? (구현을 __new__ 넣는 것 __new____new__ 된 것 같습니다.)

다음은 그 예입니다.

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

출력 :

NEW
INIT

EXISTS
INIT

EXISTS
INIT

왜?


Answers

이 문서를 참조하십시오 :

숫자 나 문자열과 같은 불변의 빌트인 타입을 서브 클래스화할 때, 때로는 다른 상황에서 새로운 정적 메소드가 유용하게 사용됩니다. newinit 이전에 호출 된 인스턴스 생성의 첫 번째 단계입니다.

메소드는 클래스를 첫 번째 인수로 호출합니다. 그것의 책임은 그 클래스의 새로운 인스턴스를 반환하는 것이다.

이것을 init 과 비교해보십시오 : init 는 인스턴스를 첫 번째 인수로 사용하여 호출되며 아무 것도 반환하지 않습니다. 그 책임은 인스턴스를 초기화하는 것입니다.

init 을 호출하지 않고 새 인스턴스가 생성되는 상황이 있습니다 (예를 들어 인스턴스가 피클에서로드 될 때). new를 호출하지 않고 새 인스턴스를 만들 수있는 방법은 없습니다 (경우에 따라 기본 클래스의 클래스를 호출하지 않아도 됨).

달성하고자하는 것에 관해서는, 싱글 톤 패턴에 관한 동일한 문서 정보도 있습니다

class Singleton(object):
        def __new__(cls, *args, **kwds):
            it = cls.__dict__.get("__it__")
            if it is not None:
                return it
            cls.__it__ = it = object.__new__(cls)
            it.init(*args, **kwds)
            return it
        def init(self, *args, **kwds):
            pass

데코레이터를 사용하여 PEP 318에서이 구현을 사용할 수도 있습니다

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
...

이제는 같은 문제가 있습니다. 몇 가지 이유에서 저는 데코레이터, 공장 및 메타 클래스를 피하기로 결정했습니다. 나는 이것을 이렇게했다 :

주 파일

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init


class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

예제 수업

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

사용

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • 경고 : Parent를 초기화하면 안됩니다. 다른 클래스와 충돌합니다. 각 자식에서 별도의 캐시를 정의하지 않는 한, 이는 우리가 원하는 것이 아닙니다.
  • 경고 : 조부모로서 부모를 둔 클래스가 이상하게 행동하는 것처럼 보입니다. [확인되지 않음]

온라인으로 사용해보십시오!


그러나, 나는 __init__ 이 항상 __new__ 다음에 호출되는 이유에 대해 다소 혼란스러워합니다.

그것 이외의 많은 이유가 그저 그렇게 된 것입니다. __new__ 는 클래스를 초기화 할 책임이 없으며, 다른 일부 메소드는 않습니다 ( __call__ , 아마도 - 나는 확실하지 않습니다).

나는 이것을 기대하지 않았다. 아무도 왜 이것이 일어나고 어떻게 다른 기능을 구현합니까 말해 줄래? (떨어져 꽤 __new__ 느낌이 __new__ 구현을 퍼팅).

__init__ 이 이미 초기화되었거나 아무 것도하지 않으면 새 인스턴스에 __init__ 만 호출하는 새 __call__ 을 사용하여 새 메타 클래스를 작성할 수 있습니다. 그렇지 않으면 __new__(...) 반환합니다.


가장 잘 알려진 OO 언어에서 SomeClass(arg1, arg2) 와 같은 표현식은 새 인스턴스를 할당하고 인스턴스의 속성을 초기화 한 다음 반환합니다.

가장 잘 알려진 OO 언어에서 "인스턴스의 속성 초기화"부분은 생성자 를 정의하여 각 클래스에 대해 사용자 정의 할 수 있습니다. 생성자 는 기본적으로 새 인스턴스에서 작동하는 코드 블록입니다 (생성자 표현식에 제공된 인수 사용). )를 사용하여 원하는 초기 조건을 설정하십시오. 파이썬에서는 클래스 ' __init__ 메소드에 해당합니다.

파이썬의 __new__ 은 "새로운 인스턴스 할당"부분과 비슷한 클래스 별 커스터마이징에 불과합니다. 이것은 물론 새로운 인스턴스를 할당하는 것이 아니라 기존 인스턴스를 반환하는 것과 같은 특별한 작업을 수행 할 수있게합니다. 그래서 파이썬에서, 우리는이 부분을 반드시 할당을 필요로한다고 생각해서는 안됩니다. 우리가 필요로하는 모든 것은 __new__ 이 어딘가에서 적절한 인스턴스를 __new__ 는 것입니다.

그러나 여전히 작업의 절반에 불과하며, 파이썬 시스템이 나중에 다른 작업 ( __init__ )을 실행하기를 원한다는 것을 알기가 어렵습니다. 당신이 그 행동을 원한다면, 당신은 그렇게 명시 적으로 말해야 만합니다.

종종 refactor를 사용하여 __new__ 만 필요하므로 __new__ 필요로하지 않거나 __init__ 이 이미 초기화 된 객체에서 다르게 동작하도록 할 수 있습니다. 그러나 정말로 원한다면 실제로 파이썬은 "직업"을 다시 정의 할 수있게 SomeClass(arg1, arg2) 으로써 SomeClass(arg1, arg2) 가 반드시 __new__ 다음에 __init__ 호출하지는 않습니다. 이렇게하려면 메타 클래스를 만들고 __call__ 메서드를 정의해야합니다.

메타 클래스는 클래스의 클래스 일뿐입니다. 그리고 클래스 ' __call__ 메서드는 클래스의 인스턴스를 호출 할 때 일어나는 일을 제어합니다. 따라서 메타 클래스 ' __call__ 메서드는 클래스를 호출 할 때 일어나는 일을 제어합니다. 즉 , 인스턴스 생성 메커니즘을 처음부터 끝까지 다시 정의 할 수 있습니다. 이는 싱글 톤 패턴과 같이 완전히 비표준 인 인스턴스 생성 프로세스를 가장 우아하게 구현할 수있는 수준입니다. 실제로 코드가 10 줄 미만인 Singleton 메타 클래스를 구현하면 __new__ 을 사용하지 않아도되며 __metaclass__ = Singleton !을 추가하여 다른 일반 클래스를 싱글 톤으로 설정할 수 있습니다.

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

그러나 이것은 아마도이 상황에 대해 정말로 보장받는 것보다 더 깊은 마법입니다!


나는이 질문이 꽤 오래되었다는 것을 알고 있지만 비슷한 문제가있다. 다음은 내가 원했던 것입니다 :

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

이 페이지를 리소스로 사용했습니다. http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html


documentation 인용 :

일반적인 구현은 적절한 인수로 "super (currentclass, cls) .__ new __ (cls [, ...])"를 사용하여 수퍼 클래스의 __new __ () 메소드를 호출 한 다음 필요에 따라 새로 생성 된 인스턴스를 수정하여 클래스의 새 인스턴스를 만듭니다 그것을 반환하기 전에.

...

__new __ ()가 cls의 인스턴스를 반환하지 않으면 새 인스턴스의 __init __ () 메서드가 호출되지 않습니다.

__new __ ()은 주로 int 형, str 형, 또는 tuple과 같은 불변 형의 서브 클래스가 인스턴스 생성을 커스터마이즈 할 수 있도록하기위한 것입니다.


이 질문에 대한 간단한 대답은 __new__ 이 클래스와 동일한 유형의 값을 반환하면 __init__ 함수가 실행되고, 그렇지 않으면 __init__ 함수가 실행된다는 것입니다. 이 경우 코드는 cls 와 동일한 클래스 인 A._dict('key') 를 반환하므로 __init__ 가 실행됩니다.


@AntonyHatchkins에 대한 대답으로, 메타 타입의 각 클래스에 대해 별도의 인스턴스 사전을 원할 것입니다. 즉, 모든 클래스에서 전역으로 설정하지 않고 클래스 객체를 해당 사전으로 초기화하기 위해 메타 클래스에 __init__ 메소드가 있어야합니다. .

class MetaQuasiSingleton(type):
    def __init__(cls, name, bases, attibutes):
        cls._dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print('EXISTS')
            instance = cls._dict[key]
        else:
            print('NEW')
            instance = super().__call__(key)
            cls._dict[key] = instance
        return instance

class A(metaclass=MetaQuasiSingleton):
    def __init__(self, key):
        print 'INIT'
        self.key = key
        print()

나는 원래 코드를 __init__ 업데이트하고 파이썬 3 표기법 (애트리뷰트로 대신 클래스 인자에서 super 와 metaclass를 사용하는 arg가없는 콜론)으로 구문을 변경했다.

어쨌든 중요한 점은 키가 발견되면 클래스 이니셜 라이저 ( __call__ 메서드)가 __new__ 또는 __init__ 실행하지 않는다는 것입니다. 이것은 __new__ 사용하는 것보다 훨씬 깔끔합니다. 기본 __init__ 단계를 건너 뛰려면 개체를 표시해야합니다.


전통적인 OO 언어의 간단한 생성자로 __init__ 을 살펴야합니다. 예를 들어, Java 또는 C ++에 익숙한 경우, 생성자는 암시 적으로 자체 인스턴스에 대한 포인터를 전달받습니다. Java의 this 변수입니다. Java 용으로 생성 된 바이트 코드를 검사 할 경우 두 번의 호출이 발생합니다. 첫 번째 호출은 "새로운"메소드에 대한 호출이고, 다음 호출은 init 메소드 (사용자 정의 생성자에 대한 실제 호출)에 대한 호출입니다. 이 두 단계 프로세스를 통해 해당 인스턴스의 또 다른 메소드 인 클래스의 생성자 메소드를 호출하기 전에 실제 인스턴스를 작성할 수 있습니다.

이제 Python의 경우 __new__ 은 사용자가 액세스 할 수있는 추가 기능입니다. Java는 유형이 정해져있어 유연성을 제공하지 않습니다. 언어가 그 시설을 제공한다면, __new__ 의 구현자는 인스턴스를 반환하기 전에 그 메소드에서 많은 일을 할 수 있습니다. 어떤 경우에는 무관 한 객체의 완전히 새로운 인스턴스를 생성하는 것을 포함합니다. 그리고,이 접근법은 특히 Python의 경우 불변의 타입을 위해서 잘 작동합니다.


__new__는 클래스의 새로운 빈 인스턴스를 반환해야합니다. 그런 다음 __init__을 호출하여 해당 인스턴스를 초기화합니다. __new__의 "NEW"케이스에서는 __init__을 호출하지 않으므로 호출됩니다. __new__ 호출하는 코드는 특정 인스턴스에서 __init__이 호출되었는지 여부를 추적하지 않습니다. 왜냐하면 여기서 매우 이상한 일을하고 있기 때문입니다.

__init__ 함수의 객체에 속성을 추가하여 객체가 초기화되었음을 나타낼 수 있습니다. 그 속성의 존재가 __init__의 첫 번째 것임을 확인하고 더 이상 진행하지 마십시오.


그것에 깊이 파고 들기!

CPython에서 일반적인 클래스의 유형은 type 이며 기본 클래스는 Object (메타 클래스와 같은 다른 기본 클래스를 명시 적으로 정의하지 않는 한). 저수준 호출 시퀀스는 here 에서 찾을 수 here . 호출 된 첫 번째 메소드는 type_call 메소드는 tp_new 를 호출 한 다음 tp_init 를 호출합니다.

여기서 흥미로운 부분은 tp_newObject 의 메모리를 할당하는 tp_alloc ( PyType_GenericAlloc )을 수행하는 Object 의 새로운 메소드 ( object_new )를 호출한다는 것입니다. :)

이 시점에서 객체는 메모리에 생성되고 __init__ 메쏘드가 호출됩니다. 클래스에서 __init__ 이 구현되지 않으면 object_init 가 호출되고 아무 것도 수행하지 않습니다.

그런 다음 type_call 은 변수에 바인딩하는 객체를 반환합니다.


class M(type):
    _dict = {}

    def __call__(cls, key):
        if key in cls._dict:
            print 'EXISTS'
            return cls._dict[key]
        else:
            print 'NEW'
            instance = super(M, cls).__call__(key)
            cls._dict[key] = instance
            return instance

class A(object):
    __metaclass__ = M

    def __init__(self, key):
        print 'INIT'
        self.key = key
        print

a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')

출력 :

NEW
INIT

NEW
INIT

EXISTS

주의 부작용으로 M._dict 속성은 A 에서 A._dict 로 자동 액세스 할 수 있으므로 우연히 덮어 쓰지 않도록주의하십시오.


__init____new__ 다음에 호출되므로 하위 클래스에서 재정의 할 때 추가 된 코드가 여전히 호출됩니다.

이미 __new__ 가진 클래스를 하위 클래스로 만들려고한다면, 이것을 알지 못하는 누군가는 __init__ 을 적용하고 호출을 하위 클래스 __init__ 로 전달함으로써 시작할 수 있습니다. __new__ 다음에 __init__ 을 호출하는이 규칙은 예상대로 작업하는 데 도움이됩니다.

__init__ 여전히 슈퍼 클래스 __new__ 필요한 모든 매개 변수를 허용해야하지만 그렇게하지 않으면 일반적으로 명확한 런타임 오류가 발생합니다. 그리고 __new__ 는 아마도 *args 와 '** kw'를 명시 적으로 허용해야 확장이 OK라는 것을 분명히 알 수 있습니다.

원래 포스터가 설명한 동작 때문에 동일한 수준의 상속 수준에서 동일한 클래스에 __new____init__ 을 모두 포함하는 것은 일반적으로 나쁜 형식입니다.


간단한 이유는 인스턴스가 인스턴스를 만드는 데 사용되고 init 은 인스턴스를 초기화하는 데 사용된다는 것입니다. 초기화하기 전에 먼저 인스턴스를 만들어야합니다. 그래서 newinit 전에 호출되어야 합니다 .


이것은 간단 할 수 있습니다 :

import os
cmd = "your command"
os.system(cmd)




python design-patterns class-design