¿La pregunta que estoy por hacer parece ser un duplicado del uso que hace Python de __new__ y __init__? , pero independientemente, todavía no me queda claro cuál es la diferencia práctica entre __new__
y __init__
.
Antes de que te apresures a decirme eso __new__
es para crear objetos y __init__
para inicializar objetos, déjame ser claro: lo entiendo. De hecho, esa distinción es bastante natural para mí, ya que tengo experiencia en C ++ donde tenemos una ubicación nueva , que separa de manera similar la asignación de objetos de la inicialización.
El tutorial de Python C API lo explica así:
El nuevo miembro es responsable de crear (en lugar de inicializar) objetos del tipo. Está expuesto en Python como
__new__()
método. ... Una razón para implementar un nuevo método es asegurar los valores iniciales de las variables de instancia .
Entonces, sí, entiendo lo que __new__
hace, pero a pesar de esto, yo todavía no entiendo por qué es útil en Python. El ejemplo dado dice que __new__
podría ser útil si desea "asegurar los valores iniciales de las variables de instancia". Bueno, ¿no es eso exactamente lo __init__
que hará?
En el tutorial de la API de C, se muestra un ejemplo donde se crea un nuevo Tipo (llamado "Noddy"), y el Tipo __new__
se define función . El tipo Noddy contiene un miembro de cadena llamado first
, y este miembro de cadena se inicializa en una cadena vacía de la siguiente manera:
static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
.....
self->first = PyString_FromString("");
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
.....
}
Tenga en cuenta que sin el __new__
método definido aquí, tendríamos que usar PyType_GenericNew
, que simplemente inicializa todos los miembros de la variable de instancia a NULL. Entonces, el único beneficio del __new__
método es que la variable de instancia comenzará como una cadena vacía, en lugar de NULL. Pero, ¿por qué es esto útil, ya que si nos preocupamos por asegurarnos de que nuestras variables de instancia se inicialicen con algún valor predeterminado, podríamos haberlo hecho en el __init__
método?
fuente
__new__
no es boilerplate, porque boilerplate nunca cambia. A veces necesita reemplazar ese código en particular con algo diferente.super
llamada) y devolver la instancia son partes necesarias de cualquier__new__
implementación, y la "referencia" a la que me refiero. Por el contrario,pass
es una implementación válida para__init__
: no se requiere ningún comportamiento en absoluto.Probablemente hay otros usos para,
__new__
pero hay uno realmente obvio: no se puede subclasificar un tipo inmutable sin usar__new__
. Entonces, por ejemplo, supongamos que desea crear una subclase de tupla que pueda contener solo valores integrales entre 0 ysize
.Simplemente no se puede hacer esto con
__init__
- si se trató de modificarself
en__init__
, el intérprete se queja de que usted está tratando de modificar un objeto inmutable.fuente
__new__
. Pasamoscls
explícitamente a__new__
porque, como puede leer aquí,__new__
siempre requiere un tipo como primer argumento. Luego devuelve una instancia de ese tipo. Entonces, no estamos devolviendo una instancia de la superclase, estamos devolviendo una instancia decls
. En este caso, es lo mismo que si hubiéramos dichotuple.__new__(ModularTuple, tup)
.__new__()
puede devolver objetos de tipos distintos de la clase a la que está vinculado.__init__()
solo inicializa una instancia existente de la clase.fuente
__init__
métodos que contienen un código similarself.__class__ = type(...)
. Eso hace que el objeto sea de una clase diferente a la que creías que estabas creando. Realmente no puedo cambiarlo a unoint
como lo hiciste ... Recibo un error sobre los tipos de montón o algo así ... pero mi ejemplo de asignarlo a una clase creada dinámicamente funciona.__init__()
se llama. Por ejemplo, en la respuesta de lonetwin , se llama automáticamenteTriangle.__init__()
oSquare.__init__()
automáticamente según el tipo de__new__()
retorno. Por lo que dices en tu respuesta (y he leído esto en otra parte), no parece que ninguno de ellos deba serlo, yaShape.__new__()
que no está devolviendo una instancia decls
(ni una de una subclase de ella).__init__()
métodos en la respuesta de lonetwin se llaman cuando los objetos individuales se instancian (es decir, cuando su__new__()
método regresa), no cuandoShape.__new__()
regresa.Shape.__init__()
(si tuviera uno) no se llamaría. Ahora todo tiene más sentido ...:¬)
No es una respuesta completa, pero quizás algo que ilustre la diferencia.
__new__
siempre se llamará cuando se deba crear un objeto. Hay algunas situaciones en las__init__
que no se llamará. Un ejemplo es cuando desengancha objetos de un archivo pickle, se asignarán (__new__
) pero no se inicializarán (__init__
).fuente
__new__
método es crear (esto implica la asignación de memoria) una instancia de la clase y devolverla. La inicialización es un paso separado y es lo que generalmente es visible para el usuario. Haga una pregunta por separado si hay un problema específico que enfrenta.Solo quiero agregar una palabra sobre la intención (en oposición al comportamiento) de definir
__new__
versus__init__
.Encontré esta pregunta (entre otras) cuando intentaba comprender la mejor manera de definir una fábrica de clase. Me di cuenta de que una de las formas en las que
__new__
es conceptualmente diferente__init__
es el hecho de que el beneficio de__new__
es exactamente lo que se dijo en la pregunta:Teniendo en cuenta el escenario establecido, nos preocupamos por los valores iniciales de las variables de instancia cuando la instancia es en realidad una clase en sí misma. Entonces, si estamos creando dinámicamente un objeto de clase en tiempo de ejecución y necesitamos definir / controlar algo especial sobre las instancias posteriores de esta clase que se está creando, definiríamos estas condiciones / propiedades en un
__new__
método de una metaclase.Estaba confundido sobre esto hasta que realmente pensé en la aplicación del concepto en lugar de solo el significado del mismo. Aquí hay un ejemplo que esperamos aclare la diferencia:
Tenga en cuenta que este es solo un ejemplo demoníaco. Hay varias maneras de obtener una solución sin recurrir a un enfoque de fábrica de clase como el anterior e incluso si elegimos implementar la solución de esta manera, hay algunas pequeñas advertencias por razones de brevedad (por ejemplo, declarar la metaclase explícitamente). )
Si está creando una clase regular (también conocida como no metaclase), entonces
__new__
no tiene sentido a menos que sea un caso especial como el escenario mutable versus inmutable en la respuesta de respuesta de ncoghlan (que es esencialmente un ejemplo más específico del concepto de definición los valores / propiedades iniciales de la clase / tipo que se crean a través de__new__
para luego ser inicializados a través de__init__
).fuente