Вопрос, который я собираюсь задать, похоже, повторяет использование Python __new__ и __init__? , но, тем не менее, мне все еще неясно, в чем практическая разница между __new__
и __init__
.
Прежде чем вы поспешите сказать мне, что __new__
это предназначено для создания объектов и __init__
для инициализации объектов, позвольте мне пояснить: я понимаю. Фактически, это различие вполне естественно для меня, поскольку у меня есть опыт работы с C ++, где у нас есть размещение new , которое аналогичным образом отделяет выделение объекта от инициализации.
В руководстве по Python C API это объясняется следующим образом:
Новый член отвечает за создание (в отличие от инициализации) объектов типа. Он представлен в Python как
__new__()
метод. ... Одна из причин для реализации нового метода - обеспечить начальные значения переменных экземпляра .
Итак, да - я понимаю, что __new__
делает, но, несмотря на это, я до сих пор не понимаю, почему это полезно в Python. В приведенном примере говорится, что это __new__
может быть полезно, если вы хотите «гарантировать начальные значения переменных экземпляра». Ну, разве это не то, что __init__
подойдет?
В учебнике C API показан пример, в котором создается новый тип (называемый «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__
методе?
источник
__new__
не является шаблонным, потому что шаблон никогда не изменяется. Иногда вам нужно заменить этот конкретный код чем-то другим.super
вызовом) и возврат экземпляра являются необходимыми частями любой__new__
реализации и «шаблоном», о котором я говорю. Напротив,pass
это допустимая реализация для__init__
- вообще не требуется никакого поведения.Вероятно, есть и другие варианты использования,
__new__
но есть одно действительно очевидное: вы не можете создать подкласс неизменяемого типа без использования__new__
. Так, например, предположим, что вы хотите создать подкласс кортежа, который может содержать только целые значения от 0 доsize
.Вы просто не можете сделать это с помощью
__init__
- если вы попытаетесь изменитьself
in__init__
, интерпретатор пожалуется, что вы пытаетесь изменить неизменяемый объект.источник
__new__
. Мы передаемсяcls
явно,__new__
потому что, как вы можете прочитать здесь,__new__
всегда требуется тип в качестве первого аргумента. Затем он возвращает экземпляр этого типа. Таким образом, мы не возвращаем экземпляр суперкласса - мы возвращаем экземплярcls
. В данном случае все так же, как если бы мы сказалиtuple.__new__(ModularTuple, tup)
.__new__()
может возвращать объекты типов, отличных от класса, к которому он привязан.__init__()
инициализирует только существующий экземпляр класса.источник
__init__
методы, содержащие код, похожий наself.__class__ = type(...)
. Это приводит к тому, что объект относится к классу, отличному от того, который, как вы думали, создавали. На самом деле я не могу изменить его на,int
как вы ... Я получаю сообщение об ошибке типа кучи или что-то в этом роде ... но мой пример назначения его динамически создаваемому классу работает.__init__()
называется. Например, в ответе lonetwin в , либоTriangle.__init__()
илиSquare.__init__()
автоматически вызываются в зависимости от того, какого типа__new__()
возвращается. Из того, что вы говорите в своем ответе (и я читал это в другом месте), не похоже, что ни один из них должен быть, посколькуShape.__new__()
не возвращает экземплярcls
(или один из его подклассов).__init__()
методы в ответе lonetwin вызываются, когда создаются экземпляры отдельных объектов (т.е. когда их__new__()
метод возвращается), а не приShape.__new__()
возврате.Shape.__init__()
(если бы он был) не вызвали бы. Теперь все:¬)
Не полный ответ, но, возможно, что-то, что иллюстрирует разницу.
__new__
всегда будет вызываться при создании объекта. Бывают ситуации, когда вам__init__
не позвонят. Один из примеров: когда вы извлекаете объекты из файла pickle, они получают выделенный (__new__
), но не инициализированный (__init__
).источник
__new__
метода - создать (это подразумевает выделение памяти) экземпляр класса и вернуть его. Инициализация - это отдельный шаг, и он обычно виден пользователю. Если у вас есть конкретная проблема, задайте отдельный вопрос.Просто хочу , чтобы добавить слово о намерении (в отличии от поведения) определений в
__new__
сравнении__init__
.Я столкнулся с этим вопросом (среди прочего), когда пытался понять, как лучше всего определить фабрику классов. Я понял, что одним из способов
__new__
концептуального отличия от__init__
является тот факт, что преимущество__new__
- это именно то, что было заявлено в вопросе:Учитывая заявленный сценарий, мы заботимся о начальных значениях переменных экземпляра, когда экземпляр на самом деле является самим классом. Итак, если мы динамически создаем объект класса во время выполнения и нам нужно определить / контролировать что-то особенное в последующих экземплярах этого создаваемого класса, мы должны определить эти условия / свойства в
__new__
методе метакласса.Я был сбит с толку до тех пор, пока не подумал о применении концепции, а не только о ее значении. Вот пример, который, надеюсь, прояснит разницу:
Обратите внимание, это всего лишь наглядный пример. Существует несколько способов получить решение, не прибегая к подходу фабрики классов, как указано выше, и даже если мы решим реализовать решение таким образом, для краткости остаются небольшие оговорки (например, явное объявление метакласса )
Если вы создаете обычный класс (он же неметакласс), тогда
__new__
это действительно не имеет смысла, если только это не особый случай, например изменяемый или неизменяемый сценарий в ответе ncoghlan answer (который, по сути, является более конкретным примером концепции определения начальные значения / свойства создаваемого класса / типа, которые__new__
затем инициализируются с помощью__init__
).источник