Почему __init __ () всегда вызывается после __new __ ()? - PullRequest
498 голосов
/ 23 марта 2009

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

Однако меня немного смущает вопрос, почему __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

Почему?

Ответы [ 18 ]

4 голосов
/ 24 апреля 2014

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

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

4 голосов
/ 30 сентября 2013

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

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

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

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

Есть ситуации где новый экземпляр создается без вызова init (например, когда экземпляр загружен из рассола). Нет способа создать новый экземпляр без вызова new (хотя в некоторых случаях вы можете уйти от вызова 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:
...
4 голосов
/ 15 ноября 2016

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

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

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

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

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

2 голосов
/ 12 февраля 2019

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

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

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

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

2 голосов
/ 18 июня 2012

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

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

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

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

1 голос
/ 25 июля 2018

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

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

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

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

1 голос
/ 23 марта 2018

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

1 голос
/ 23 марта 2009

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

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

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

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

...