Python (и Python C API): новый против init
вопрос, который я собираюсь задать, кажется, дубликат использование Python _ _ new_ _ и _ _ init__?, но, несмотря на это, мне все еще неясно, в чем именно практическая разница между __new__
и __init__
есть.
прежде чем вы спешите сказать мне, что __new__
предназначен для создания объектов и __init__
для инициализации объектов, позвольте мне быть ясным: я понимаю, что. на самом деле, это различие вполне естественно для меня, так как у меня есть опыт работы в C++ где мы имеем размещение new, который аналогично отделяет выделение объекта от инициализации.
на Python C API учебник объясняет это так:
новый участник несет ответственность за создание (в отличие от инициализации) объекты данного типа. Она подвергается Python как
__new__()
метод. ... одной из причин реализации нового метода является обеспечение начальных значений пример переменные.
Итак, да-я get что __new__
делает, но, несмотря на это, я еще не понимаю, почему это полезно в Python. Приведенный пример говорит, что __new__
может быть полезно, если вы хотите "гарантировать начальные значения переменных экземпляра". Ну, разве не это именно то, что __init__
будет делать?
в учебнике по API C показан пример, где создается новый тип (называемый "Noddy"), и тип __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__
способ?
5 ответов
разница в основном возникает с изменяемыми и неизменяемыми типами.
__new__
принимает тип в качестве первого аргумента, и (обычно) возвращает новый экземпляр этого типа. Таким образом соответствующе для пользы и с mutable и неизменяемыми типами.
__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__
методы легче писать, и изменяемое vs неизменяемое различие, отмеченное выше, разделение также может быть использовано для вызова родительского класса __init__
в подклассах необязательно, настраивая любые абсолютно необходимые инварианты экземпляра в __new__
. Это, как правило, сомнительная практика, хотя-обычно яснее просто называть родительский класс __init__
методы по мере необходимости.
вероятно, есть и другие виды использования для __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__
, интерпретатор будет жаловаться, что вы пытаетесь изменить неизменяемый объект.
__new__()
может возвращать объекты типов, отличных от класса, к которому он привязан. __init__()
инициализирует только существующий экземпляр класса.
>>> class C(object):
... def __new__(cls):
... return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
не полный ответ, но, возможно, что-то, что иллюстрирует разницу.
__new__
всегда будет вызван, когда объект должен быть создан. Есть некоторые ситуации, когда __init__
не будет вызван. Например, когда вы распаковываете объекты из файла рассола, они будут выделены (__new__
), но не инициализирован (__init__
).
просто хочу добавить слово о намерение (в отличие от поведения) определения __new__
и __init__
.
я столкнулся с этим вопросом (среди прочих), когда пытался понять лучший способ определения фабрики классов. Я понял, что один из способов, которым __new__
концептуально отличается от __init__
является тот факт, что в пользу __new__
это именно то, что указано в вопросе:
так единственное преимущество метод _ _ new_ _ заключается в том, что переменная экземпляра будет начинаться как пустая строка, в отличие от NULL. Но почему это всегда полезно, Так как если бы мы заботились о том, чтобы наши переменные инициализируются в значение по умолчанию, мы могли бы просто сделать это в __инит__ способ?
учитывая изложенный сценарий, мы заботимся о начальных значениях переменных экземпляра, когда экземпляр на самом деле сам класс. Итак, если мы динамически создавая объект класса во время выполнения, и нам нужно определить / контролировать что-то особенное о последующих экземплярах этого создаваемого класса, мы определили бы эти условия/свойства в __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`s ? 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
обратите внимание, что это просто пример demonstartive. Существует несколько способов получить решение, не прибегая к подходу фабрики классов, как указано выше, и даже если мы решим имплантировать решение таким образом, есть несколько предостережений, оставленных для краткости (например, объявление метакласса явно)
если вы создаете обычный класс (a.к. a non-metaclass), то __new__
на самом деле не имеет смысла, если это не особый случай, такой как изменяемый или неизменяемый сценарий в ответ ncoghlan ответ (который по существу является более конкретным примером концепции определения начальных значений / свойств класса / типа, создаваемого с помощью __new__
для инициализации через __init__
).