Почему __init__() всегда вызывается после __new__()?

Я просто пытаюсь упростить один из моих классов и ввел некоторые функциональные возможности в том же стиле, что и шаблон проектирования в стиле " легкий вес".

Тем не менее, я немного запутался, почему __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

Зачем?

20 ответов

Решение

Используйте __new__, когда вам нужно контролировать создание нового экземпляра. Используйте __init__, когда вам нужно контролировать инициализацию нового экземпляра.

__new__ - это первый шаг создания экземпляра. Сначала он вызывается и отвечает за возврат нового экземпляра вашего класса. Напротив, __init__ ничего не возвращает; он отвечает только за инициализацию экземпляра после его создания.

В общем случае вам не нужно переопределять __new__, если вы не наследуете неизменяемый тип, такой как str, int, unicode или tuple.

От: http://mail.python.org/pipermail/tutor/2008-April/061426.html

Вы должны учитывать, что то, что вы пытаетесь сделать, обычно делается на фабрике, и это лучший способ сделать это. Использование __new__ не является хорошим чистым решением, поэтому, пожалуйста, рассмотрите использование фабрики. Вот вам хороший заводской пример.

__new__ метод статического класса, в то время как __init__ это метод экземпляра. __new__ должен сначала создать экземпляр, так __init__ может инициализировать это. Обратите внимание, что __init__ принимает self в качестве параметра. Пока ты не создаешь экземпляр нет self,

Теперь я понял, что вы пытаетесь реализовать шаблон синглтона в Python. Есть несколько способов сделать это.

Также, начиная с 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:
  ...

В большинстве известных ОО-языков выражение типа SomeClass(arg1, arg2) выделит новый экземпляр, инициализирует атрибуты экземпляра, а затем вернет его.

В большинстве известных ОО-языков часть "инициализировать атрибуты экземпляра" может быть настроена для каждого класса путем определения конструктора, который по сути является просто блоком кода, который работает с новым экземпляром (используя аргументы, предоставленные выражению конструктора).) для установки любых начальных условий. В Python это соответствует классу __init__ метод.

Питона __new__ это ничто иное, как похожая настройка для каждого класса для части "выделить новый экземпляр". Это, конечно, позволяет вам делать необычные вещи, такие как возврат существующего экземпляра, а не выделение нового. Так что в Python мы не должны думать об этой части как об обязательном распределении; все, что нам нужно, это __new__ придумывает подходящий экземпляр откуда-то.

Но это все еще только половина работы, и у системы Python нет возможности узнать, что иногда вы хотите запустить другую половину работы (__init__) потом, а иногда нет. Если вы хотите такое поведение, вы должны сказать об этом явно.

Часто вы можете рефакторинг, поэтому вам нужно только __new__ или тебе не нужно __new__ или так, чтобы __init__ ведет себя по-разному на уже инициализированном объекте. Но если вы действительно хотите, Python действительно позволяет вам переопределить "работу", так что SomeClass(arg1, arg2) не обязательно звонить __new__ с последующим __init__, Для этого вам нужно создать метакласс и определить его __call__ метод.

Метакласс - это просто класс класса. И класс __call__ Метод контролирует, что происходит, когда вы вызываете экземпляры класса. Так что метакласс __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

Однако это, вероятно, более глубокая магия, чем на самом деле оправдано для этой ситуации!

Цитировать документацию:

Типичные реализации создают новый экземпляр класса, вызывая метод __new __ () суперкласса, используя "super(currentclass, cls).__new__(cls[, ...])" с соответствующими аргументами, а затем изменяя вновь созданный экземпляр по мере необходимости. прежде чем вернуть его.

...

Если __new __ () не возвращает экземпляр cls, то метод нового экземпляра __init __ () вызываться не будет.

__new __ () предназначен главным образом для того, чтобы подклассы неизменяемых типов (например, int, str или tuple) могли настраивать создание экземпляров.

Я понимаю, что этот вопрос довольно старый, но у меня была похожая проблема. Следующие сделали то, что я хотел:

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

Когда __new__ возвращает экземпляр того же класса, __init__ запускается впоследствии на возвращенном объекте. Т.е. вы не можете использовать __new__ предотвращать __init__ от запуска. Даже если вы возвращаете ранее созданный объект из __new__, это будет двойной (тройной и т. д.) инициализируется __init__ опять и опять.

Вот общий подход к шаблону Singleton, который расширяет ответ vartec выше и исправляет его:

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,

Я думаю, что простой ответ на этот вопрос заключается в том, что если __new__ возвращает значение того же типа, что и класс, __init__ Функция выполняется, иначе не будет. В этом случае ваш код возвращается A._dict('key') который того же класса, что и cls, так __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__ метод и изменил синтаксис в нотации Python 3 (без аргументов вызов super и метакласс в аргументах класса, а не как атрибут).

В любом случае, важным моментом здесь является то, что ваш инициализатор класса (__call__ метод) также не будет выполняться __new__ или же __init__ если ключ найден. Это намного чище, чем при использовании __new__, который требует, чтобы вы отметили объект, если вы хотите пропустить по умолчанию __init__ шаг.

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 так что будьте осторожны, чтобы не перезаписать его случайно.

Ссылаясь на этот документ:

При создании подклассов неизменяемых встроенных типов, таких как числа и строки, а иногда и в других ситуациях, статический метод new оказывается полезным. new - это первый шаг в создании экземпляра, который вызывается до init.

Новый метод вызывается с классом в качестве первого аргумента; его обязанность - вернуть новый экземпляр этого класса.

Сравните это с init: init вызывается с экземпляром в качестве первого аргумента, и он ничего не возвращает; его ответственность заключается в инициализации экземпляра.

Существуют ситуации, когда новый экземпляр создается без вызова init (например, когда экземпляр загружается из маринада). Невозможно создать новый экземпляр без вызова нового (хотя в некоторых случаях вы можете избежать вызова нового базового класса).

Относительно того, чего вы хотите достичь, в том же документе есть информация о паттерне Singleton.

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

Однако меня немного смущает вопрос, почему init всегда вызывается после new.

Я думаю, что здесь была бы полезна аналогия с C++: (A) new просто выделяет память для объекта. Переменные экземпляра объекта нуждаются в памяти для его хранения, и это то, что сделал бы новый шаг. (B) init инициализирует внутренние переменные объекта конкретными значениями (может быть по умолчанию).

Надо смотреть на __init__ как простой конструктор в традиционных языках ОО. Например, если вы знакомы с Java или C++, конструктору неявно передается указатель на его собственный экземпляр. В случае с Java это this переменная. Если бы нужно было проверить байт-код, сгенерированный для Java, можно было бы заметить два вызова. Первый вызов относится к "новому" методу, а затем следующий вызов к методу init (который является фактическим вызовом определяемого пользователем конструктора). Этот двухэтапный процесс позволяет создать фактический экземпляр перед вызовом метода конструктора класса, который является просто еще одним методом этого экземпляра.

Теперь, в случае с Python, __new__ это дополнительная возможность, которая доступна для пользователя. Java не обеспечивает такой гибкости из-за своего типизированного характера. Если язык предоставил это средство, то разработчик __new__ мог бы сделать много вещей в этом методе перед возвратом экземпляра, включая создание совершенно нового экземпляра несвязанного объекта в некоторых случаях. И этот подход также хорошо работает, особенно для неизменяемых типов в случае с Python.

__new__ должен возвращать новый пустой экземпляр класса. Затем вызывается __init__ для инициализации этого экземпляра. Вы не звоните __init__ в "новом" случае __new__, поэтому он вызывается для вас. Код, который вызывает __new__ не отслеживает, был ли __init__ вызван в конкретном экземпляре или нет, и не должен, потому что вы делаете здесь что-то очень необычное.

Вы можете добавить атрибут к объекту в функции __init__, чтобы указать, что он был инициализирован. Сначала проверьте наличие этого атрибута в __init__ и больше не продолжайте, если он был.

Копаем немного глубже в это!

Тип универсального класса в CPython: type и его базовый класс Object (Если вы явно не определите другой базовый класс как метакласс). Последовательность вызовов низкого уровня можно найти здесь. Первый метод называется type_call который затем вызывает tp_new а потом tp_init,

Интересная часть здесь заключается в том, что tp_new позвоню Object's (базовый класс) новый метод object_new который делает tp_alloc (PyType_GenericAlloc) который выделяет память для объекта:)

В этот момент объект создается в памяти, а затем __init__ метод вызывается. Если __init__ не реализован в вашем классе, то object_init вызывается и ничего не делает:)

затем type_call просто возвращает объект, который привязывается к вашей переменной.

Теперь у меня та же проблема, и по некоторым причинам я решил избегать декораторов, фабрик и метаклассов. Я сделал это так:

Основной файл

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, он будет конфликтовать с другими классами - если вы не определили отдельный кеш в каждом из дочерних элементов, это не то, что нам нужно.
  • Предупреждение: Кажется, класс с Parent, как дедушка ведет себя странно. [Непроверенные]

Попробуйте онлайн!

__init__ называется после __new__ так что когда вы переопределите его в подклассе, ваш добавленный код все равно будет вызываться.

Если вы пытаетесь создать подкласс для класса, который уже имеет __new__, кто-то не подозревая об этом, может начать с адаптации __init__ и переадресация вызова на подкласс __init__, Это соглашение о призвании __init__ после __new__ помогает этой работе, как ожидалось.

__init__ по-прежнему необходимо учитывать любые параметры суперкласса __new__ необходимо, но неспособность сделать это обычно приводит к явной ошибке во время выполнения. И __new__ должно явно разрешить *args и "**kw", чтобы было ясно, что расширение в порядке.

Как правило, плохо иметь __new__ а также __init__ в том же классе на том же уровне наследования, из-за поведения, описанного оригинальным плакатом.

При создании экземпляра класса сначала вызывается для создания экземпляра класса , затем вызывается для инициализации экземпляра .

:

Вызывается для создания нового экземпляра класса cls. ...

Если __new__() вызывается во время создания объекта и возвращает экземпляр cls, то метод __init__() нового экземпляра будет вызываться как __init__(self[, ...]),...

:

Вызывается после создания экземпляра (с помощью __new__()),...

Поскольку __new__() и __init__() работают вместе при создании объектов (__new__() для их создания и __init__() для их настройки),...

Например, при создании экземпляра класса сначала вызывается __new__()__new__()__новый__() для создания экземпляра класса , затем вызывается __init__()__init__()__инициализация__() для инициализации экземпляра , как показано ниже:

      class Teacher:
    def __init__(self, name):
        self.name = name
        
class Student:
    def __init__(self, name):
        self.name = name

obj = Teacher("John") # Instantiation

print(obj.name)

Это результат:

      <class '__main__.Teacher'>
John

И, используя__new__()экземпляра _ Teacherclass , мы можем создать экземпляр класса , как показано ниже:

      # ...

obj = Teacher("John")
print(type(obj))
print(obj.name)

obj = obj.__new__(Student) # Creates the instance of "Student" class
print(type(obj))

Теперь экземпляр класса создается, как показано ниже:

      <class '__main__.Teacher'>
<__main__.Teacher object at 0x7f4e3950bf10>
<class '__main__.Student'> # Here

Далее, если мы попытаемся получить значение переменной из ** экземпляра класса , как показано ниже:

      obj = Teacher("John")
print(type(obj))
print(obj.name)

obj = obj.__new__(Student)
print(type(obj))
print(obj.name) # Tries to get the value of "name" variable

Ошибка ниже возникает из-за того, что экземпляр класса не был инициализирован__init__()еще:

AttributeError: объект «Студент» не имеет атрибута «имя»

Итак, мы инициализируем экземпляр класса , как показано ниже:

      obj = Teacher("John") 
print(type(obj))
print(obj.name)

obj = obj.__new__(Student)
print(type(obj))
obj.__init__("Tom") # Initializes the instance of "Student" class
print(obj.name)

Тогда мы можем получить значение nameпеременная из экземпляра Studentкласс , как показано ниже:

      <class '__main__.Teacher'>
John
<class '__main__.Student'>
Tom # Here

Тем не менее, я немного запутался, почему __init__ всегда вызывается после __new__,

Не так много причин, кроме того, что это просто сделано таким образом. __new__ не несет ответственности за инициализацию класса, какой-то другой метод делает (__call__возможно... я точно не знаю)

Я не ожидал этого. Может кто-нибудь сказать мне, почему это происходит и как я реализую эту функцию в противном случае? (кроме помещения реализации в __new__ который чувствует себя довольно взломанным).

Вы могли бы иметь __init__ ничего не делать, если он уже инициализирован, или вы можете написать новый метакласс с новым __call__ это только звонки __init__ на новых экземплярах, а в противном случае просто возвращается __new__(...),

Простая причина в том, что new используется для создания экземпляра, в то время как init используется для инициализации экземпляра. Перед инициализацией экземпляр должен быть создан первым. Вот почему new следует вызывать перед init.

Люди уже подробно изложили вопрос и ответили на них, используя некоторые примеры, такие как синглтон и т. Д. См. Код ниже:

      __instance = None

def __new__(cls):
    if cls.__instance is None:
        cls.__instance = object.__new__(cls)
    return cls.__instance

Я получил приведенный выше код по этой ссылке , в нем есть подробный обзор new vs init . Стоит прочтения!

Другие вопросы по тегам