Почему __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
Зачем?
20 ответов
Используйте __new__, когда вам нужно контролировать создание нового экземпляра. Используйте __init__, когда вам нужно контролировать инициализацию нового экземпляра.
__new__ - это первый шаг создания экземпляра. Сначала он вызывается и отвечает за возврат нового экземпляра вашего класса. Напротив, __init__ ничего не возвращает; он отвечает только за инициализацию экземпляра после его создания.
В общем случае вам не нужно переопределять __new__, если вы не наследуете неизменяемый тип, такой как str, int, unicode или tuple.
От: http://mail.python.org/pipermail/tutor/2008-April/061426.html
Вы должны учитывать, что то, что вы пытаетесь сделать, обычно делается на фабрике, и это лучший способ сделать это. Использование __new__ не является хорошим чистым решением, поэтому, пожалуйста, рассмотрите использование фабрики. Вот вам хороший заводской пример.
__new__
метод статического класса, в то время как __init__
это метод экземпляра. __new__
должен сначала создать экземпляр, так __init__
может инициализировать это. Обратите внимание, что __init__
принимает self
в качестве параметра. Пока ты не создаешь экземпляр нет self
,
Теперь я понял, что вы пытаетесь реализовать шаблон синглтона в 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:
...
В большинстве известных ОО-языков выражение типа SomeClass(arg1, arg2)
выделит новый экземпляр, инициализирует атрибуты экземпляра, а затем вернет его.
В большинстве известных ОО-языков часть "инициализировать атрибуты экземпляра" может быть настроена для каждого класса путем определения конструктора, который по сути является просто блоком кода, который работает с новым экземпляром (используя аргументы, предоставленные выражению конструктора).) для установки любых начальных условий. В Python это соответствует классу __init__
метод.
Питона __new__
это ничто иное, как похожая настройка для каждого класса для части "выделить новый экземпляр". Это, конечно, позволяет вам делать необычные вещи, такие как возврат существующего экземпляра, а не выделение нового. Так что в Python мы не должны думать об этой части как об обязательном распределении; все, что нам нужно, это __new__
придумывает подходящий экземпляр откуда-то.
Но это все еще только половина работы, и у системы Python нет возможности узнать, что иногда вы хотите запустить другую половину работы (__init__
) потом, а иногда нет. Если вы хотите такое поведение, вы должны сказать об этом явно.
Часто вы можете рефакторинг, поэтому вам нужно только __new__
или тебе не нужно __new__
или так, чтобы __init__
ведет себя по-разному на уже инициализированном объекте. Но если вы действительно хотите, Python действительно позволяет вам переопределить "работу", так что SomeClass(arg1, arg2)
не обязательно звонить __new__
с последующим __init__
, Для этого вам нужно создать метакласс и определить его __call__
метод.
Метакласс - это просто класс класса. И класс __call__
Метод контролирует, что происходит, когда вы вызываете экземпляры класса. Так что метакласс __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__
запускается впоследствии на возвращенном объекте. Т.е. вы не можете использовать __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
,
Я думаю, что простой ответ на этот вопрос заключается в том, что если __new__
возвращает значение того же типа, что и класс, __init__
Функция выполняется, иначе не будет. В этом случае ваш код возвращается A._dict('key')
который того же класса, что и cls
, так __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 (без аргументов вызов super
и метакласс в аргументах класса, а не как атрибут).
В любом случае, важным моментом здесь является то, что ваш инициализатор класса (__call__
метод) также не будет выполняться __new__
или же __init__
если ключ найден. Это намного чище, чем при использовании __new__
, который требует, чтобы вы отметили объект, если вы хотите пропустить по умолчанию __init__
шаг.
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
так что будьте осторожны, чтобы не перезаписать его случайно.
При создании подклассов неизменяемых встроенных типов, таких как числа и строки, а иногда и в других ситуациях, статический метод new оказывается полезным. new - это первый шаг в создании экземпляра, который вызывается до init.
Новый метод вызывается с классом в качестве первого аргумента; его обязанность - вернуть новый экземпляр этого класса.
Сравните это с init: init вызывается с экземпляром в качестве первого аргумента, и он ничего не возвращает; его ответственность заключается в инициализации экземпляра.
Существуют ситуации, когда новый экземпляр создается без вызова init (например, когда экземпляр загружается из маринада). Невозможно создать новый экземпляр без вызова нового (хотя в некоторых случаях вы можете избежать вызова нового базового класса).
Относительно того, чего вы хотите достичь, в том же документе есть информация о паттерне 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:
...
Однако меня немного смущает вопрос, почему init всегда вызывается после new.
Я думаю, что здесь была бы полезна аналогия с C++: (A) new просто выделяет память для объекта. Переменные экземпляра объекта нуждаются в памяти для его хранения, и это то, что сделал бы новый шаг. (B) init инициализирует внутренние переменные объекта конкретными значениями (может быть по умолчанию).
Надо смотреть на __init__
как простой конструктор в традиционных языках ОО. Например, если вы знакомы с Java или C++, конструктору неявно передается указатель на его собственный экземпляр. В случае с Java это this
переменная. Если бы нужно было проверить байт-код, сгенерированный для Java, можно было бы заметить два вызова. Первый вызов относится к "новому" методу, а затем следующий вызов к методу init (который является фактическим вызовом определяемого пользователем конструктора). Этот двухэтапный процесс позволяет создать фактический экземпляр перед вызовом метода конструктора класса, который является просто еще одним методом этого экземпляра.
Теперь, в случае с Python, __new__
это дополнительная возможность, которая доступна для пользователя. Java не обеспечивает такой гибкости из-за своего типизированного характера. Если язык предоставил это средство, то разработчик __new__
мог бы сделать много вещей в этом методе перед возвратом экземпляра, включая создание совершенно нового экземпляра несвязанного объекта в некоторых случаях. И этот подход также хорошо работает, особенно для неизменяемых типов в случае с Python.
__new__ должен возвращать новый пустой экземпляр класса. Затем вызывается __init__ для инициализации этого экземпляра. Вы не звоните __init__ в "новом" случае __new__, поэтому он вызывается для вас. Код, который вызывает __new__
не отслеживает, был ли __init__ вызван в конкретном экземпляре или нет, и не должен, потому что вы делаете здесь что-то очень необычное.
Вы можете добавить атрибут к объекту в функции __init__, чтобы указать, что он был инициализирован. Сначала проверьте наличие этого атрибута в __init__ и больше не продолжайте, если он был.
Копаем немного глубже в это!
Тип универсального класса в 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
просто возвращает объект, который привязывается к вашей переменной.
Теперь у меня та же проблема, и по некоторым причинам я решил избегать декораторов, фабрик и метаклассов. Я сделал это так:
Основной файл
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, он будет конфликтовать с другими классами - если вы не определили отдельный кеш в каждом из дочерних элементов, это не то, что нам нужно.
- Предупреждение: Кажется, класс с Parent, как дедушка ведет себя странно. [Непроверенные]
__init__
называется после __new__
так что когда вы переопределите его в подклассе, ваш добавленный код все равно будет вызываться.
Если вы пытаетесь создать подкласс для класса, который уже имеет __new__
, кто-то не подозревая об этом, может начать с адаптации __init__
и переадресация вызова на подкласс __init__
, Это соглашение о призвании __init__
после __new__
помогает этой работе, как ожидалось.
__init__
по-прежнему необходимо учитывать любые параметры суперкласса __new__
необходимо, но неспособность сделать это обычно приводит к явной ошибке во время выполнения. И __new__
должно явно разрешить *args
и "**kw", чтобы было ясно, что расширение в порядке.
Как правило, плохо иметь __new__
а также __init__
в том же классе на том же уровне наследования, из-за поведения, описанного оригинальным плакатом.
При создании экземпляра класса сначала вызывается для создания экземпляра класса , затем вызывается для инициализации экземпляра .
:
Вызывается для создания нового экземпляра класса cls. ...
Если __new__() вызывается во время создания объекта и возвращает экземпляр cls, то метод __init__() нового экземпляра будет вызываться как __init__(self[, ...]),...
:
Вызывается после создания экземпляра (с помощью __new__()),...
Поскольку __new__() и __init__() работают вместе при создании объектов (__new__() для их создания и __init__() для их настройки),...
Например, при создании экземпляра класса сначала вызывается __new__()__new__()__новый__() для создания экземпляра класса , затем вызывается __init__()__init__()__инициализация__() для инициализации экземпляра , как показано ниже:
class Teacher:
def __init__(self, name):
self.name = name
class Student:
def __init__(self, name):
self.name = name
obj = Teacher("John") # Instantiation
print(obj.name)
Это результат:
<class '__main__.Teacher'>
John
И, используя__new__()
экземпляра _
Teacher
class , мы можем создать экземпляр класса , как показано ниже:
# ...
obj = Teacher("John")
print(type(obj))
print(obj.name)
obj = obj.__new__(Student) # Creates the instance of "Student" class
print(type(obj))
Теперь экземпляр класса создается, как показано ниже:
<class '__main__.Teacher'>
<__main__.Teacher object at 0x7f4e3950bf10>
<class '__main__.Student'> # Here
Далее, если мы попытаемся получить значение переменной из ** экземпляра класса , как показано ниже:
obj = Teacher("John")
print(type(obj))
print(obj.name)
obj = obj.__new__(Student)
print(type(obj))
print(obj.name) # Tries to get the value of "name" variable
Ошибка ниже возникает из-за того, что экземпляр класса не был инициализирован__init__()
еще:
AttributeError: объект «Студент» не имеет атрибута «имя»
Итак, мы инициализируем экземпляр класса , как показано ниже:
obj = Teacher("John")
print(type(obj))
print(obj.name)
obj = obj.__new__(Student)
print(type(obj))
obj.__init__("Tom") # Initializes the instance of "Student" class
print(obj.name)
Тогда мы можем получить значение
name
переменная из экземпляра
Student
класс , как показано ниже:
<class '__main__.Teacher'>
John
<class '__main__.Student'>
Tom # Here
Тем не менее, я немного запутался, почему
__init__
всегда вызывается после__new__
,
Не так много причин, кроме того, что это просто сделано таким образом. __new__
не несет ответственности за инициализацию класса, какой-то другой метод делает (__call__
возможно... я точно не знаю)
Я не ожидал этого. Может кто-нибудь сказать мне, почему это происходит и как я реализую эту функцию в противном случае? (кроме помещения реализации в
__new__
который чувствует себя довольно взломанным).
Вы могли бы иметь __init__
ничего не делать, если он уже инициализирован, или вы можете написать новый метакласс с новым __call__
это только звонки __init__
на новых экземплярах, а в противном случае просто возвращается __new__(...)
,
Простая причина в том, что new используется для создания экземпляра, в то время как init используется для инициализации экземпляра. Перед инициализацией экземпляр должен быть создан первым. Вот почему new следует вызывать перед init.
Люди уже подробно изложили вопрос и ответили на них, используя некоторые примеры, такие как синглтон и т. Д. См. Код ниже:
__instance = None
def __new__(cls):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance
Я получил приведенный выше код по этой ссылке , в нем есть подробный обзор new vs init . Стоит прочтения!