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

Почему?

447
23 марта '09 в 20:13
источник поделиться
17 ответов

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

__ новый __ - это первый шаг создания экземпляра. Он называется первым, и ответственный за возвращение нового экземпляр вашего класса. В противоположность, __ init __ ничего не возвращает; он отвечает только за инициализацию после его создания.

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

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

Вы должны подумать, что то, что вы пытаетесь сделать, обычно делается с помощью Factory и что это лучший способ сделать это. Использование __ нового __ не является хорошим чистым решением, поэтому, пожалуйста, рассмотрите использование factory. Здесь хороший factory пример.

473
23 марта '09 в 20:23
источник

Связанные вопросы


Похожие вопросы

__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:
  ...
146
23 марта '09 в 20:20
источник

В наиболее известных языках 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

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

124
29 дек. '12 в 10:39
источник

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

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

...

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

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

20
23 марта '09 в 20:29
источник

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

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

10
13 мая '13 в 17:12
источник

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

8
07 июля '13 в 17:45
источник

Когда __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
27 янв. '15 в 0:25
источник
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, поэтому будьте осторожны, чтобы не перезаписывать его случайно.

4
30 июля '15 в 15:04
источник

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

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

4
23 марта '09 в 20:23
источник

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

При подклассификации неизменяемых встроенных типов, таких как числа и строки, и иногда в других ситуациях возникает статический метод новыйв удобном виде. Новый - это первый шаг в построении экземпляра, вызываемый до 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:
...
3
30 сент. '13 в 9:13
источник

Обновление для @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__ по умолчанию.

3
18 июля '17 в 22:39
источник

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

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

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

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

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

3
15 нояб. '16 в 23:47
источник

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

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

3
24 апр. '14 в 4:30
источник

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

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

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

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

1
18 июня '12 в 19:30
источник

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

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

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

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

0
23 марта '09 в 20:23
источник

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

0
23 марта '18 в 16:48
источник

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

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

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

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

0
25 июля '18 в 22:21
источник

Посмотрите другие вопросы по меткам или Задайте вопрос