Python (и Python C API): новый по сравнению с init
вопрос, который я собираюсь задать, кажется, дубликат использование Python _ _ new__ и _ _ init__?, но, несмотря на это, мне все еще неясно, в чем именно заключается практическая разница между __new__ и __init__ есть.
прежде чем ты поспешишь сказать мне это __new__ предназначен для создания объектов и __init__ для инициализации объектов, позвольте мне быть ясным: я понимаю, что. на самом деле, это различие вполне естественно для меня, так как у меня есть опыт работы в C++ где мы имеем размещение нового, который аналогично отделяет выделение объекта от инициализации.
The Python C API tutorial объясняет это так:
новый член несет ответственность за создание (в отличие от инициализации) объекты этого типа. Она подвергается Питон как то
__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__принимает a тип в качестве первого аргумента, и (обычно) возвращает новый экземпляр этого типа. Таким образом, он подходит для использования как изменяемые и неизменяемые типы.
__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__методы по мере необходимости.
есть, вероятно, и другие применения для
__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обратите внимание, что это просто показательный пример. Существует несколько способов получить решение, не прибегая к фабричному подходу класса, как указано выше, и даже если мы решим внедрить решение таким образом, есть несколько предостережений, оставленных для краткости (например, объявление метакласса явно)
если вы создаете обычный класс (он же неметакласс), то
__new__на самом деле не имеет смысла, если это не особый случай, такой как изменяемый или неизменяемый сценарий в ncoghlan это ответ (который по существу является более конкретным примером концепции определения начальных значений / свойств класса / типа, создаваемого с помощью__new__для инициализации через__init__).