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