Что может `__init__` делать, что` __new__` не может?
В Python __new__
используется для инициализации неизменяемых типов, а __init__
обычно инициализирует изменяемые типы. Если __init__
были удалены с языка, что больше нельзя было сделать (легко)?
Например,
class A:
def __init__(self, *, x, **kwargs):
super().__init__(**kwargs)
self.x = x
class B(A):
def __init__(self, y=2, **kwargs):
super().__init__(**kwargs)
self.y = y
Можно переписать с помощью __new__
следующим образом:
class A_N:
def __new__(cls, *, x, **kwargs):
obj = super().__new__(cls, **kwargs)
obj.x = x
return obj
class B_N(A_N):
def __new__(cls, y=2, **kwargs):
obj = super().__new__(cls, **kwargs)
obj.y = y
return obj
Разъяснение для области вопроса: это не вопрос о том, как используются __init__
и __new__
или в чем разница между ними. Это вопрос о том, что произойдет, если __init__
были удалены с языка. Что-нибудь сломается? Становится ли что-нибудь труднее или невозможнее?
Ответы
Ответ 1
Все, что вы можете сделать в __init__
, также можно сделать в __new__
.
Тогда зачем использовать __init__
?
Потому что вам не нужно хранить экземпляр в переменной (obj
в вашем примере кода), а затем возвращать его. Вы можете сосредоточиться на том, что вы действительно хотите сделать - ndash; инициализация изменяемого объекта.
Ответ 2
Обратите внимание на разницу между __new__ и __init __
Прежде чем объяснять недостающие функциональные возможности, вернитесь к определению __new__
и __init__
:
__ новый __ - это первый шаг создания экземпляра. Он называется первым и отвечает за возврат нового экземпляра вашего класса.
Однако __ init__ ничего не возвращает; он несет ответственность только за
инициализируя экземпляр после его создания.
Последствия замены __init__ на __new __
В основном вы потеряете гибкость. Вы получите много семантических головных болей и раздельного разделения инициализатора и конструкции (присоединяя __new__ and
init, мы должны присоединиться к конструкции и инициализации в один шаг...).
Давайте рассмотрим фрагмент ниже:
class A(object):
some_property = 'some_value'
def __new__(cls, *args, **kwargs):
obj = object.__new__(cls, *args, **kwargs)
obj.some_property = cls.some_property
return obj
class B(A):
some_property = 2
def __new__(cls, *args, **kwargs):
obj = super(B, cls).__new__(cls)
return obj
Последствия перемещения действий __init__
в __new__
:
-
Инициализировать B
до A
: когда вы используете __new__
вместо __init__
, ваш первый шаг создания нового экземпляра B вызывает A.__new__
как побочный эффект, вы не можете инициализировать B
до инициализации A
(доступ и назначение некоторых свойств новому экземпляру B). Использование __init__
дает такую гибкость.
-
Свободный контроль над порядком инициализации: предположим, что у вас есть B_N
, унаследованный от двух классов (A_N1
, A_N2
), теперь вы пропустите управление порядком инициализации нового экземпляра B_N
( какой порядок вы собираетесь инициализировать экземпляры? Это может быть важно... что странно.)
-
Свойства и методы беспорядок: вы пропустили бы доступ к A.some_property
(cls
был бы равен B
при создании нового экземпляра B
. Однако возможен прямой доступ к A.some_property
, но я предполагаю, что он, по крайней мере, странный для доступа к свойствам внутри класса classt, а не с помощью classmethods
).
-
Вы не можете повторно инициализировать существующий экземпляр, не создавая для него новую или специальную логику реализации (благодаря идее @platinhom)
Что может __init__
сделать, что __new__
не может?
В __new__
нет действий, которые не могут быть выполнены в __init__
, поскольку действия, которые __init__
выполняет , являются подмножеством действий, которые могут выполняться с помощью __new__
.
Интересный момент из Документы Python, Распиловка и рассыпание обычных экземпляров классов # object. getinitargs относительно __init__
может быть полезно:
Когда экземпляр маринованного экземпляра рассыпается, его метод init() обычно не вызывается.
Ответ 3
Per When to use __new__ vs. __init__
__new__
- это первый шаг создания экземпляра. Он назвал сначала, и отвечает за возвращение нового экземпляра вашего класса. В контраст, __init__
ничего не возвращает; он несет ответственность только за инициализируя экземпляр после его создания.
Также класс класса type
, а type.__call__()
реализован примерно так, как показано ниже:
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
if isinstance(obj, cls):
obj.__init__(*args, **kwargs)
return obj
Мы знаем __init__()
, только часть части __new__()
может сделать что-либо для этого объекта до того, как объект будет возвращен.
В общем случае вам не нужно переопределять __new__
, если вы не подклассифицируете неизменяемый тип типа str, int, unicode или кортеж.
Поэтому нехорошо удалять __init__
с языка, и лучше всегда использовать __init__()
лучше, чем использовать __new__()
.
Вот одна история Low-level constructors and __new__()
.