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

Почему?

Ответы

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

Теперь, я понимаю, вы пытаетесь реализовать singleton pattern в 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:
  ...

Ответ 3

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

В наиболее известных языках OO атрибут "initialize the instance attributes" может быть настроен для каждого класса, указав конструктор , который в основном представляет собой блок кода, который работает на новом instance (используя аргументы, предоставленные выражению конструктора), чтобы настроить любые начальные условия. В Python это соответствует методу класса __init__.

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

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

Часто вы можете реорганизовать, так что вам нужно только __new__, или вам не нужно __new__, или так, чтобы __init__ вел себя по-другому по уже инициализированному объекту. Но если вы действительно этого хотите, Python действительно позволяет вам переопределить "задание", чтобы 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

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

Ответ 4

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

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

...

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

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

Ответ 5

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

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

Ответ 6

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

Ответ 7

Когда __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__, заключается в использовании classmethods:

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.

Ответ 8

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

Ответ 9

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

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

Ответ 10

Обновление для @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 (вызов no-arg для super и метакласс в аргументах класса вместо атрибута).

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

Ответ 11

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

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

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

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

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

Относительно того, чего вы хотите достичь, также в одной и той же док-информации о шаблоне 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:
...

Ответ 12

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

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

Ответ 13

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

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

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

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

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

Ответ 14

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

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

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

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

Ответ 15

Однако меня немного смущает вопрос, почему __init__ всегда __new__ после __new__.

Я думаю, что аналогия C++ была бы полезна здесь:

  1. __new__ просто выделяет память для объекта. Переменные экземпляра объекта нуждаются в памяти для его хранения, и это то, что __new__ бы шаг __new__.

  2. __init__ инициализирует внутренние переменные объекта определенными значениями (может быть по умолчанию).

Ответ 16

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

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

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

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

Ответ 17

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

Ответ 18

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

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

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

Попробуйте прямо сейчас!