__init __ ()이 왜 항상 __new __ () 뒤에 호출됩니까?


568

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

그러나 왜 __init__항상 호출 된지에 대해 약간 혼란 스럽습니다 __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

왜?


플라이급 디자인 패턴과 거의 모든 대중적인 언어에서 예제를 가진 아주 좋은 링크를 들었습니다.
EmmaYang

답변:


598

__new__새 인스턴스 작성을 제어해야 할 때 사용하십시오 .

__init__새 인스턴스의 초기화를 제어해야 할 때 사용하십시오 .

__new__인스턴스 생성의 첫 단계입니다. 먼저 호출되며 클래스의 새 인스턴스를 반환합니다.

반대로, __init__아무것도 반환하지 않습니다. 인스턴스를 만든 후에 만 ​​초기화해야합니다.

일반적으로 __new__str, int, unicode 또는 tuple과 같은 불변 유형을 서브 클래스 화하지 않는 한 재정의 할 필요가 없습니다 .

2008년 4월 게시물에서 : 때 사용하는 __new__대를 __init__? mail.python.org에서.

당신이하려는 일이 일반적으로 팩토리로 이루어지며 그것이 최선의 방법 이라는 것을 고려해야 합니다. 사용 __new__하는 것이 좋은 해결책은 아니므로 공장의 사용을 고려하십시오. 여기 좋은 공장 예가 있습니다.


1
__new__Factory 클래스 내부에서 사용 되었으므로 꽤 깨끗해 졌으므로 입력 해 주셔서 감사합니다.
Dan

11
죄송합니다. 사용이 __new__명시된 경우로 엄격하게 제한되어야 한다는 데 동의하지 않습니다 . 확장 가능한 일반 클래스 팩토리를 구현하는 데 매우 유용하다는 것을 알았습니다 . Python에서 클래스를 생성 하는 부적절한 사용법에 대한 답변 을 참조하십시오 . 예를 들어. __new__
martineau

170

__new__정적 클래스 메서드이고 __init__인스턴스 메서드입니다. __new__인스턴스를 먼저 만들어야하므로 __init__초기화 할 수 있습니다. 주 __init__소요 self매개 변수로. 인스턴스를 만들 때까지는 없습니다 self.

이제 파이썬에서 싱글 톤 패턴 을 구현하려고 노력하고 있습니다. 몇 가지 방법이 있습니다.

또한 Python 2.6부터 클래스 데코레이터를 사용할 수 있습니다 .

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

@singleton
class MyClass:
  ...

8
@Tyler Long, "@singleton"이 어떻게 작동하는지 잘 모르겠습니다. 데코레이터가 함수를 반환하지만 MyClass는 클래스이기 때문입니다.
Alcott

11
왜 사전인가요? cls는 항상 동일하고 각 싱글 톤에 대한 새로운 사전을 얻으므로 하나의 항목 만 포함하는 사전을 만듭니다.
Winston Ewert

7
@Alcott : 의견이 없습니다 . 문서 가 귀하에게 동의합니다.
Ethan Furman

2
@ 알콧. 예, 데코레이터는 함수를 반환합니다. 그러나 클래스와 함수는 모두 호출 가능합니다. 인스턴스 = {}는 전역 변수 여야한다고 생각합니다.
Tyler Long

4
@TylerLong Alcott는 좋은 지적이 있습니다. 이로 인해 MyClass원래 클래스의 인스턴스를 반환하는 함수에 이름 이 바인딩됩니다. 그러나 이제는 원래 클래스를 전혀 참조 할 수있는 방법이 없으며 MyClass함수 중단 isinstance/ issubclass검사, 클래스 속성 / 메소드에 직접 액세스 MyClass.something, super호출 에서 클래스 이름 지정 등 등이 있습니다. 당신이 그것을 적용하는 수업 (및 나머지 프로그램).
Ben

148

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

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

파이썬 __new__은 "새 인스턴스 할당"부분의 비슷한 클래스 별 사용자 정의에 지나지 않습니다. 물론 새 인스턴스를 할당하는 대신 기존 인스턴스를 반환하는 등의 비정상적인 작업을 수행 할 수 있습니다. 파이썬에서 우리는이 부분이 반드시 할당과 관련이 있다고 생각해서는 안됩니다. 우리가 필요로하는 것은 __new__어딘가에서 적합한 인스턴스를 만들어 내는 것입니다.

그러나 그것은 여전히 ​​작업의 절반에 불과하며, 파이썬 시스템이 때로는 작업의 다른 절반을 실행하고 싶을 때가 있다는 것을 알 __init__수있는 방법이 없습니다. 당신이 그 행동을 원한다면, 당신은 그렇게 분명하게 말해야합니다.

종종 리팩토링하여 필요 __new__하거나 필요하지 않거나 이미 초기화 된 객체에서 다르게 동작 __new__하도록 할 수 __init__있습니다. 당신이 정말로, 파이썬은 실제로 수 있습니까하려는 경우 그이 있도록하지만, "작업"을 다시 정의 할 SomeClass(arg1, arg2)필요는 호출하지 않습니다 __new__다음에 __init__. 이렇게하려면 메타 클래스를 작성하고 해당 __call__메소드를 정의해야 합니다.

메타 클래스는 클래스의 클래스 일뿐입니다. 그리고 클래스의 __call__메소드는 클래스의 인스턴스를 호출 할 때 발생하는 일을 제어합니다. 따라서 metaclass ' __call__메소드는 클래스를 호출 할 때 발생하는 상황을 제어합니다. 즉 , 인스턴스 생성 메커니즘을 처음부터 끝까지 재정의 할 수 있습니다 . 싱글 톤 패턴과 같은 완전히 비표준 인스턴스 생성 프로세스를 가장 우아하게 구현할 수있는 수준입니다. 사실, 코드의 10 개 라인 당신이 구현할 수있는 Singleton다음도 futz에 당신을 필요로하지 않는 메타 클래스를 __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

그러나 이것은 아마도이 상황에서 보장되는 것보다 더 깊은 마법 일 것입니다!


25

설명서 를 인용하려면 :

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

...

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

__new __ ()는 주로 변경 불가능한 유형의 하위 클래스 (int, str 또는 tuple과 같은)가 인스턴스 작성을 사용자 정의 할 수 있도록하기위한 것입니다.


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.그것은 중요한 포인트입니다. 다른 인스턴스를 반환하면 원본 __init__이 호출되지 않습니다.
Jeffrey Jose

@ tgray 나는 당신의 대답이 문서에서 온 것을 알고 있지만 cls 인스턴스를 반환하지 않는 유스 케이스를 알고 있다면 궁금합니다. 반환 된 객체 __new__가 클래스에 대해 검사 될 때 메소드는 실패한 검사가 자동으로 전달되는 대신 오류를 발생시킵니다. 왜냐하면 클래스의 객체 이외의 것을 반환하려는 이유를 이해하지 못하기 때문입니다. .
soporific312

@ soporific312 사용 사례를 보지 못했습니다. 이 답변 에서는 디자인에 대한 몇 가지 추론에 대해 설명하지만 "기능"을 활용하는 코드는 보지 못했습니다.
tgray

12

이 질문은 상당히 오래되었지만 비슷한 문제가 있음을 알고 있습니다. 다음은 내가 원하는 것을했습니다.

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 로 사용했습니다.


7
참고 : __new__는 항상 적절한 객체를 반환하므로 인스턴스가 이미 존재하더라도 __init__이 항상 호출됩니다.
Oddthinking

10

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


10

__new__같은 클래스의 인스턴스를 반환 하면 __init__나중에 반환 된 객체에서 실행됩니다. 즉, 실행 __new__을 막기 위해 사용할 수 없습니다 __init__. 에서 이전에 생성 된 객체를 반환하더라도 반복 __new__해서 두 번 초기화됩니다 (트리플 등) __init__.

여기에 vartec 답변을 확장하고 수정하는 Singleton 패턴에 대한 일반적인 접근 방식이 있습니다.

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

전체 이야기는 여기에 있습니다 .

실제로 관련된 또 다른 접근법 __new__은 클래스 메소드를 사용 하는 것 입니다.

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

이 방법을 @classmethod사용하면의 실제 인스턴스를 사용하지 않으므로 모든 메소드를로 장식해야합니다 MyClass.


6

이 문서를 참조하십시오 :

숫자 및 문자열과 같은 변경 불가능한 내장 유형을 서브 클래 싱하고 때로는 다른 상황에서 정적 메소드 new 가 유용합니다. new 는 인스턴스 생성의 첫 번째 단계이며 init 전에 호출됩니다 .

새로운 방법은 첫 번째 인수로 클래스를 호출합니다; 그 책임은 해당 클래스의 새 인스턴스를 반환하는 것입니다.

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

init 를 호출하지 않고 새 인스턴스가 작성되는 상황이 있습니다 (예 : 피클에서 인스턴스를로드하는 경우). 호출하지 않고 새 인스턴스를 만들 수있는 방법이 없습니다 (경우에 따라서는 기본 클래스의 호출 넘어갈 수 있지만 새가 ).

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

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:
...

1
정말 해키 소리 init에서 나쁜 놈 형태를 부르는 것 __new__. 이것이 메타 클래스입니다.
미친 물리학 자

6
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

NB로 부작용의 M._dict속성은 자동에서 액세스 될 A대로 A._dict그렇게 우연히 그것을 덮어 쓰지 않도록주의해야합니다.


__init__설정 하는 메소드 가 누락되었습니다 cls._dict = {}. 이 메타 타입의 모든 클래스가 공유하는 사전을 원하지 않을 수도 있습니다 (하지만 아이디어의 경우 +1).
미친 물리학 자

5

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

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


5

@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__메소드를 사용 하여 원래 코드를 업데이트하고 구문을 Python 3 표기법으로 변경했습니다 ( super속성 대신 클래스 인수에서 인수 없음 및 메타 클래스 없음 ).

어느 쪽이든, 여기에서 중요한 점은 클래스의 초기화 (즉 __call__방법) 중 하나를 실행되지 않습니다 __new__또는 __init__키가 발견 된 경우. 이것은 __new__기본 __init__단계 를 건너 뛰려면 객체를 표시 해야하는을 사용 하는 것보다 훨씬 깨끗 합니다 .


4

조금 더 깊이 파고 들다!

CPython에서 일반 클래스의 유형은 type기본 클래스이며 Object메타 클래스와 같은 다른 기본 클래스를 명시 적으로 정의하지 않는 한 기본 클래스는 다음과 같습니다. 낮은 수준의 통화 순서는 여기에서 확인할 수 있습니다 . 첫 번째 type_call로 호출되는 메소드는 호출 tp_new한 다음 tp_init입니다.

여기서 흥미로운 부분 은 객체의 메모리를 할당 하는 ( )를 수행하는 의 (기본 클래스) 새로운 메소드 를 tp_new호출 한다는 것 입니다. :)Objectobject_newtp_allocPyType_GenericAlloc

이 시점에서 객체가 메모리에 생성 된 후 __init__메소드가 호출됩니다. 경우 __init__다음 클래스에서 구현되지 않은이 object_init호출되는 그것은 아무것도하지 않습니다 :)

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


4

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

이제 Python의 경우 __new__사용자가 액세스 할 수있는 추가 기능이 있습니다. Java는 유형 특성으로 인해 유연성을 제공하지 않습니다. 언어가 해당 기능을 제공 한 경우 구현 __new__자는 인스턴스를 반환하기 전에 해당 메서드에서 여러 가지 작업을 수행 할 수 있습니다. 경우에 따라 관련이없는 객체의 완전히 새로운 인스턴스를 만드는 것도 포함됩니다. 그리고이 접근법은 특히 파이썬의 경우 불변 유형에 대해서도 잘 작동합니다.


4

그러나 왜 __init__항상 호출 된지에 대해 약간 혼란 스럽습니다 __new__.

C ++ 유추가 여기에 유용 할 것이라고 생각합니다.

  1. __new__단순히 객체에 메모리를 할당합니다. 객체의 인스턴스 변수는 그것을 보유하기 위해 메모리가 필요하며, 이것이 단계 __new__가 수행하는 것입니다.

  2. __init__ 객체의 내부 변수를 특정 값으로 초기화합니다 (기본값 일 수 있음).


2

__init__후라고 __new__하면 하위 클래스에서 재정의 할 때, 귀하의 추가 코드가 여전히 전화를받을 수 있도록.

이미 클래스가있는 클래스를 서브 클래스로 만들려고하는 경우 __new__이를 인식하지 못하는 사람 __init__은 호출을 서브 클래스 에 적용 하고 전달하여 시작할 수 있습니다 __init__. 호출이 협약 __init__이후는 __new__예상대로 그 일을하는 데 도움이됩니다.

__init__여전히 슈퍼 클래스가 매개 변수를 허용 할 필요가 __new__필요하지만, 그렇지 않을 경우 일반적으로 명확한 런타임 오류를 생성합니다. 그리고 __new__아마도 *args확장이 정상임을 분명히하기 위해 아마도 명시 적으로 '** kw'를 허용해야합니다 .

모두 가지고 일반적으로 나쁜 형태 __new____init__때문에 원래 포스터가 설명하는 행동, 상속의 동일한 수준에서 같은 클래스에 있습니다.


1

그러나 왜 __init__항상 호출 된지에 대해 약간 혼란 스럽습니다 __new__.

그 이외의 이유는 많지 않습니다. __new__클래스를 초기화 할 책임이 없으며 다른 방법은 수행합니다 ( __call__, 아마도 모르겠습니다).

나는 이것을 기대하지 않았다. 왜 이런 일이 발생하는지, 그렇지 않으면 어떻게이 기능을 구현하는지 말해 줄 수 있습니까? (구현을 구현하는 __new__것을 제외하고는 상당히 해키 느낌).

당신은 할 수 없었다 __init__이미 초기화되어있어 경우 수행 아무것도하거나 새와 새로운 메타 클래스 쓸 수 __call__만 호출 __init__새로운 인스턴스에, 그렇지 않으면 그냥 돌아갑니다 __new__(...).


1

간단한 이유는 new 가 인스턴스를 만드는 데 사용되고 init 는 인스턴스를 초기화하는 데 사용되기 때문입니다. 초기화하기 전에 인스턴스를 먼저 작성해야합니다. 그래서 init 전에 new 를 호출해야 합니다 .


1

이제 같은 문제가 발생했으며 어떤 이유로 데코레이터, 공장 및 메타 클래스를 피하기로 결정했습니다. 나는 이렇게했다 :

메인 파일

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 클래스가있는 것처럼 보입니다. [확인되지 않음]

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.