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

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

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

почему?

17 ответов


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

_ _ new__ является первым шагом создания экземпляра. Это называется first, и это ответственный за возвращение нового экземпляр вашего класса. В отличие от, _ _ init__ ничего не возвращает; он отвечает только за инициализацию экземпляр после он был создан.

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

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

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


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

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

кроме того, начиная с Python 2.6, вы можете использовать class декораторы.

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

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

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

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

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

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

метакласс-это просто класс класса. И класс!--12--> метод управляет тем, что происходит при вызове экземпляров класса. Так метакласс' __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__ функция выполняется, иначе она не будет. В этом случае ваш код возвращает A._dict('key') который является тем же классом, что и cls, так что __init__ будет выполнен.


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

вот общий подход к Одноэлементному шаблону, который расширяет ответ 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.


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


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

вы можете добавить атрибут к объекту в функцию __init__, чтобы указать, что это инициализирован. Проверьте наличие этого атрибута как первое в __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 (вызов no-arg на super и метакласс в аргументы класса вместо атрибута).

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


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

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

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

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

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

Что касается того, что вы хотите достичь, там же doc информация о Singleton pattern

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

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

тип универсального класса в 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 просто возвращает объект, который привязывается к вашему переменной.


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

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


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

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

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

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


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

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

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

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


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


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

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

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