Python (и API Python C): __new__ против __init__
Вопрос, который я собираюсь задать, кажется дубликат использования Python __new__ и __init__?, но, несмотря на это, мне все еще неясно, что именно практическая разница между __new__
и __init__
равна.
Прежде чем спешить, чтобы сказать мне, что __new__
предназначен для создания объектов, а __init__
- для инициализации объектов, позвольте мне быть ясным: Я получаю это. На самом деле это различие вполне естественно для меня, поскольку у меня есть опыт работы на С++, где у нас размещение нового, что аналогично отделяет выделение объектов от инициализации.
Учебник Python C API объясняет это следующим образом:
Новый член отвечает за создание (в отличие от инициализации) объектов типа. Он подвергается воздействию Python как метод __new__()
.... Одной из причин внедрения нового метода является обеспечение начальных значений переменные экземпляра.
Итак, да, я понимаю, что делает __new__
, но, несмотря на это, я до сих пор не понимаю, почему он полезен в Python. В приведенном примере говорится, что __new__
может оказаться полезным, если вы хотите "заверить начальные значения переменных экземпляра". Ну, разве это не то, что сделает __init__
?
В учебнике API C показан пример, где создается новый тип (называемый "Noddy" ), и определена функция Type __new__
. Тип Noddy содержит член строки first
, и этот член строки инициализируется пустой строкой, например:
static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
.....
self->first = PyString_FromString("");
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
.....
}
Обратите внимание, что без метода __new__
, определенного здесь, нам нужно будет использовать PyType_GenericNew
, который просто инициализирует все члены переменной экземпляра NULL. Таким образом, единственное преимущество метода __new__
заключается в том, что переменная экземпляра начнется как пустая строка, а не NULL. Но почему это когда-либо полезно, так как если бы мы заботились о том, чтобы наши переменные экземпляра были инициализированы некоторым значением по умолчанию, мы могли бы просто сделать это в методе __init__
?
Ответы
Ответ 1
Разница в основном возникает с изменчивыми vs неизменяемыми типами.
__new__
принимает тип в качестве первого аргумента и (обычно) возвращает новый экземпляр этого типа. Таким образом, он подходит для использования с изменяемыми и неизменяемыми типами.
__init__
принимает экземпляр как первый аргумент и изменяет атрибуты этого экземпляра. Это неуместно для неизменяемого типа, поскольку это позволит им изменять после создания, вызывая obj.__init__(*args)
.
Сравните поведение tuple
и list
:
>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]
Что касается того, почему они являются отдельными (помимо простых исторических причин): методы __new__
требуют, чтобы куча шаблона была правильной (первоначальное создание объекта, а затем вспоминая, чтобы вернуть объект в конце). __init__
методы, напротив, просты, поскольку вы просто устанавливаете все атрибуты, которые вам нужно установить.
Помимо методов __init__
, которые легче писать, и измененного или неизменяемого различия, отмеченного выше, разделение также может быть использовано для того, чтобы сделать необязательным вызов родительского класса __init__
в подклассах, установив любые абсолютно необходимые инварианты экземпляра в __new__
. Это, как правило, является сомнительной практикой - обычно проще вызывать методы родительского класса __init__
по мере необходимости.
Ответ 2
Есть, вероятно, другие применения для __new__
, но есть один действительно очевидный: вы не можете подклассифицировать неизменяемый тип без использования __new__
. Например, предположим, что вы хотите создать подкласс кортежа, который может содержать только целые значения между 0 и size
.
class ModularTuple(tuple):
def __new__(cls, tup, size=100):
tup = (int(x) % size for x in tup)
return super(ModularTuple, cls).__new__(cls, tup)
Вы просто не можете сделать это с помощью __init__
- если вы попытались изменить self
в __init__
, интерпретатор будет жаловаться на то, что вы пытаетесь изменить неизменяемый объект.
Ответ 3
__new__()
может возвращать объекты типов, отличных от класса, к которому он привязан. __init__()
инициализирует только существующий экземпляр класса.
>>> class C(object):
... def __new__(cls):
... return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
Ответ 4
Не полный ответ, но, возможно, что-то, что иллюстрирует разницу.
__new__
всегда будет вызываться, когда объект должен быть создан. Есть ситуации, когда __init__
не будет вызван. Например, когда вы разбрасываете объекты из файла pickle, они будут выделены (__new__
), но не инициализированы (__init__
).
Ответ 5
Просто хочу добавить слово о намерении (в отличие от поведения) определения __new__
versus __init__
.
Я столкнулся с этим вопросом (среди прочих), когда я пытался понять, как лучше определить фабрику классов. Я понял, что одним из способов, по которым __new__
концептуально отличается от __init__
является тот факт, что преимущество __new__
- это именно то, что было указано в вопросе:
Таким образом, единственное преимущество метода __new__ заключается в том, что переменная экземпляра начнется как пустая строка, а не NULL. Но почему это когда-либо полезно, так как если бы мы заботились о том, чтобы наши переменные экземпляра были инициализированы некоторым значением по умолчанию, мы могли бы просто сделать это в методе __init__?
Учитывая указанный сценарий, мы заботимся об исходных значениях переменных экземпляра, когда экземпляр на самом деле является самим классом. Итак, если мы динамически создаем объект класса во время выполнения, и нам нужно определить/управлять чем-то особенным о последующих экземплярах создаваемого класса, мы будем определять эти условия/свойства в методе __new__
для метакласса.
Я был смущен этим, пока я не думал о применении концепции, а не просто о ее значении. Вот пример, который, мы надеемся, сделает разницу ясными:
a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())
# I want 'a' and 'b' to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the '.area()' method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of 'if ? Here is one possibility
class Shape:
def __new__(cls, sides, *args, **kwargs):
if sides == 3:
return Triangle(*args, **kwargs)
else:
return Square(*args, **kwargs)
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return (self.base * self.height) / 2
class Square:
def __init__(self, length):
self.length = length
def area(self):
return self.length*self.length
Обратите внимание, что это просто демонстративный пример. Существует несколько способов получить решение, не прибегая к подходу к фабричному классу, как описано выше, и даже если мы сделаем это для решения проблемы, для краткости осталось несколько предостережений (например, объявление метакласса явно )
Если вы создаете обычный класс (aka non-metaclass), то __new__
самом деле не имеет смысла, если это не особый случай, как изменчивый или неизменяемый сценарий в ответе ответа ncoghlan (что по сути является более конкретным примером концепции определяя начальные значения/свойства класса/типа, создаваемые с помощью __new__
чтобы затем инициализироваться через __init__
).