Почему __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

Почему?


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

Ответы:


598

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

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

__new__это первый шаг создания экземпляра. Сначала он вызывается и отвечает за возврат нового экземпляра вашего класса.

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

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

С апреля 2008 пост: когда использовать __new__против __init__? на mail.python.org.

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


1
Закончилось использование __new__внутри класса Factory, который стал довольно чистым, так что спасибо за ваш вклад.
Дан

11
Извините, я не согласен с тем, что использование __new__должно быть строго ограничено указанными случаями. Я нашел это очень полезным для реализации расширяемых универсальных фабрик классов - см. Мой ответ на вопрос « Неправильное использование __new__для генерации классов в Python»? для примера этого.
Мартино

171

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

8
@ Тайлер Долго, я не совсем понимаю, как работает "@singleton"? Потому что декоратор возвращает функцию, а MyClass - это класс.
Олкотт

11
Почему словарь? Поскольку cls всегда будет одинаковым, и вы получите свежий словарь, например, для каждого синглтона, вы создаете словарь, содержащий только один элемент.
Уинстон Эверт

7
@ Олкотт: Мнение не требуется - документы согласны с вами.
Итан Фурман

2
@Alcott. да, декоратор возвращает функцию. но и класс, и функция являются вызываемыми. Я думаю, что instances = {} должна быть глобальной переменной.
Тайлер Лонг

4
@TylerLong Олкотт имеет хорошую точку зрения. В результате имя MyClassсвязывается с функцией, которая возвращает экземпляры исходного класса. Но теперь нет никакого способа обратиться к исходному классу вообще, и, MyClassбудучи функцией, ломает isinstance/ issubclassпроверяет, получает доступ к атрибутам / методам класса напрямую как MyClass.something, называет класс в superвызовах и т. Д., И т. Д., И т. Д., Является ли это проблемой или нет, зависит от класс, к которому вы применяете это (и остальная часть программы).
Бен

149

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

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

Python - __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

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


25

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

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

...

Если __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__никогда не вызывается.
Джеффри Хосе

@tgray Я понимаю, что ваш ответ из документации, но мне любопытно, если вы знаете какой-либо вариант использования, не возвращающий экземпляр cls. Кажется, что когда возвращаемый объект __new__проверяется на предмет его класса, метод выдаст ошибку, а не пропустит неудачную проверку, потому что я не понимаю, почему вы захотите вернуть что-либо, кроме объекта класса ,
soporific312

@ soporific312 Я не видел ни одного варианта использования. В этом ответе обсуждаются некоторые причины разработки, хотя они также не видели ни одного кода, использующего эту «функцию».
tgray

13

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

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__ - даже если экземпляр уже существует.
Нечетное

10

Я думаю, что простой ответ на этот вопрос заключается в том, что, если __new__возвращает значение того же типа, что и класс, __init__функция выполняется, в противном случае она не будет. В этом случае ваш код возвращает A._dict('key')тот же класс cls, что __init__и будет выполнен.


10

Когда __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.


6

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

При создании подклассов неизменяемых встроенных типов, таких как числа и строки, а иногда и в других ситуациях, статический метод 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:
...

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__ для инициализации этого экземпляра. Вы не звоните __init__ в «новом» случае __new__, поэтому он вызывается для вас. Код, который вызывает __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вызов Objectнового метода (базового класса), object_newкоторый выполняет tp_alloc( PyType_GenericAlloc), который выделяет память для объекта :)

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

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


4

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

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


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 используется для инициализации экземпляра. Перед инициализацией экземпляр должен быть создан первым. Вот почему new должен быть вызван перед init .


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

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.