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

Почему?


person Dan    schedule 23.03.2009    source источник
comment
тоже пытался понять шаблон проектирования, и впервые услышал о шаблоне проектирования легковесного ... и очень хорошей ссылке, имеющей пример почти на всех популярных языках.   -  person EmmaYang    schedule 08.05.2020
comment
разве это не синглтон?   -  person derek    schedule 13.03.2021


Ответы (18)


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

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

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

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

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

Из сообщения от апреля 2008 года: Когда использовать __new__ против _7 _? на mail.python.org.

Вы должны учитывать, что то, что вы пытаетесь сделать, обычно делается с помощью Factory, и это лучший вариант. способ сделать это. Использование __new__ не является хорошим чистым решением, поэтому рассмотрите возможность использования фабрики. Вот хороший пример: ActiveState Fᴀᴄᴛᴏʀʏ ᴘᴀᴛᴛᴇʀɴ Рецепт.

person Community    schedule 23.03.2009
comment
Закончился использованием __new__ внутри класса Factory, который стал довольно чистым, поэтому спасибо за ваш вклад. - person Dan; 25.03.2009
comment
Извините, я не согласен с тем, что использование __new__ должно быть строго ограничено указанными случаями. Я нашел его очень полезным для реализации расширяемых фабрик универсальных классов - см. мой ответ на вопрос Неправильное использование __new__ для создания классов в Python? в качестве примера. - person martineau; 03.02.2015
comment
Я категорически не согласен с тем, что __new__ здесь плохое решение. Шаблон фабрики необходим в языках, которые ограничивают конструкторы для работы в качестве инициализаторов (не позволяя вам возвращать существующий объект); как и большинство шаблонов проектирования (особенно их множество, изобретенных специально для Java в первые дни из-за негибкости языка), это способ последовательного обхода языковых ограничений. Python не имеет этого ограничения; вы используете __new__ для этого случая и @classmethod альтернативные конструкторы для построения из вариантных аргументов. - person ShadowRanger; 22.06.2020
comment
Ссылка на Factory pattern Recipe не работает. - person paradox; 18.06.2021

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

Как я понимаю, вы пытаетесь реализовать шаблон singleton на 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:
  ...
person vartec    schedule 23.03.2009
comment
@Tyler Long, я не совсем понимаю, как работает @singleton? Потому что декоратор возвращает функцию, а MyClass - это класс. - person Alcott; 19.09.2011
comment
Почему словарь? Поскольку cls всегда будет одним и тем же, и вы получите новый словарь, например, для каждого синглтона, вы создаете словарь только с одним элементом в нем. - person Winston Ewert; 20.09.2011
comment
@Alcott: мнение не требуется - документы согласен с тобой. - person Ethan Furman; 20.09.2011
comment
@Alcott. да, декоратор возвращает функцию. но и класс, и функция являются вызываемыми. Я думаю, что instance = {} должна быть глобальной переменной. - person Tyler Long; 12.11.2011
comment
new - статический метод. См. Объединение типов и классов в Python 2.2 (python.org/download/releases/2.2 .3 / descrintro). - person bhadra; 02.02.2012
comment
@TylerLong Олкотт прав. Это приводит к привязке имени MyClass к функции, возвращающей экземпляры исходного класса. Но теперь вообще нет возможности ссылаться на исходный класс, и MyClass, будучи функцией, прерывает _3 _ / _ 4_ проверки, доступ к атрибутам / методам класса напрямую как MyClass.something, присвоение имени классу в super вызовах и т. Д. И т. Д. Является ли это проблемой или нет, зависит от класса, к которому вы его применяете (и остальной части программы). - person Ben; 22.03.2012
comment
@TokenMacGuy, если кто-то попытается вызвать __new__ напрямую, он потребует явной передачи аргумента класса, поэтому это статический метод, как говорит Гвидо; но на самом деле, когда класс A вызывается для создания экземпляра A(*arguments), он вызывает __new__(A, *arguments), беззвучно передавая класс. - person ジョージ; 07.05.2012
comment
@TylerLong Уинстон Эверт прав в том, что экземпляры являются локальной переменной. добавьте print len(instances) в начало getinstance (), и он будет всегда 0 или 1, даже если дополнительные классы обернуты с помощью @singleton - person tacaswell; 21.06.2012
comment
Однако, после небольшого толчка, экземпляры должны быть изменяемыми, чтобы закрытие работало (я думаю). - person tacaswell; 21.06.2012
comment
Пока вы не создадите экземпляр, self. Это прояснило ситуацию, спасибо! - person Bora M. Alper; 15.03.2015
comment
MyClass - это класс, но оказывается, что это функция после декорирования singleton , вызовет ли это проблемы? - person Bin; 25.10.2016
comment
Вызов new статического метода и init в качестве метода экземпляра действительно помогли мне решить эту проблему. Спасибо! - person Asher Mancinelli; 13.01.2018

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

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

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

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

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

Метакласс - это просто класс класса. А метод __call__ класса управляет тем, что происходит, когда вы вызываете экземпляры класса. Итак, метод метакласса '__call__ управляет тем, что происходит, когда вы вызываете класс; то есть позволяет переопределить механизм создания экземпляров от начала до конца. Это уровень, на котором вы можете наиболее элегантно реализовать совершенно нестандартный процесс создания экземпляра, такой как шаблон singleton. Фактически, с менее чем 10 строками кода вы можете реализовать Singleton метакласс, который тогда даже не требует, чтобы вы использовали __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

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

person Ben    schedule 29.12.2011
comment
Потрясающе: o просто и не делает работу в классе раздражающей, в отличие от украшения. - person Break; 24.01.2021

Чтобы процитировать документацию:

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

...

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

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

person tgray    schedule 23.03.2009
comment
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked. Это важный момент. Если вы вернете другой экземпляр, исходный __init__ никогда не будет вызван. - person Jeffrey Jose; 30.08.2012
comment
@tgray Я понимаю, что ваш ответ взят из документации, но мне любопытно, знаете ли вы какой-либо вариант использования не возвращать экземпляр cls. Похоже, что когда возвращаемый объект __new__ проверяется на предмет его класса, метод выдает ошибку, вместо того, чтобы позволить неудачной проверке пройти тихо, потому что я не понимаю, почему вы когда-либо захотите вернуть что-либо, кроме объекта класс. - person soporific312; 20.04.2020
comment
@ soporific312 Я не видел вариантов использования. В этом ответе обсуждаются некоторые аргументы в пользу дизайна, хотя они также не видели никакого кода, который использовал бы эту функцию. - person tgray; 28.04.2020

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

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

person Community    schedule 13.05.2013
comment
Примечание. __New__ всегда возвращает соответствующий объект, поэтому всегда вызывается __init__, даже если экземпляр уже существует. - person Oddthinking; 03.10.2014

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

person Zaar Hai    schedule 26.01.2015

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

person PMN    schedule 07.07.2013

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

При создании подклассов неизменяемых встроенных типов, таких как числа и строки, а иногда и в других ситуациях, пригодится статический метод new. 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:
...
person kiriloff    schedule 30.09.2013
comment
Вызывать убогую форму init из __new__ звучит действительно хакерски. Для этого нужны метаклассы. - person Mad Physicist; 18.07.2017

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

person Antony Hatchkins    schedule 30.07.2015
comment
Вам не хватает __init__ метода, который устанавливает cls._dict = {}. Вы, вероятно, не хотите, чтобы словарь был общим для всех классов этого метатипа (но +1 за идею). - person Mad Physicist; 18.07.2017

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

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

person Andrew Wilkinson    schedule 23.03.2009

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

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

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

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

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

person xpapazaf    schedule 15.11.2016

Обновление ответа @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__.

person Mad Physicist    schedule 18.07.2017

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

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

person grdvnl    schedule 24.04.2014

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

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

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

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

person HAltos    schedule 11.02.2019

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

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

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

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

person Jay Obermark    schedule 18.06.2012

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

Главный файл

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

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

person Community    schedule 25.07.2018

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

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

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

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

person Devin Jeanpierre    schedule 23.03.2009

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

person ZQ Hu    schedule 23.03.2018