with framework espaƱol python c python-c-api

framework - Python(y Python C API):__new__ versus__init__



python documentation (5)

La pregunta que voy a hacer parece ser un duplicado del uso de Python de __new__ y __init__? , pero independientemente, aún no está claro cuál es la diferencia práctica entre __new__ y __init__ .

Antes de apresurarse a decirme que __new__ es para crear objetos y __init__ es para inicializar objetos, déjenme ser claro: lo entiendo. De hecho, esa distinción es bastante natural para mí, ya que tengo experiencia en C ++ donde tenemos la ubicación nueva , que de manera similar separa 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 el __new__() . ... Una razón para implementar un nuevo método es asegurar los valores iniciales de las variables de instancia .

Entonces, sí, obtengo lo que __new__ hace, pero a pesar de esto, 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 que __init__ hará?

En el tutorial de C API, se muestra un ejemplo donde se crea un nuevo Tipo (llamado "Noddy") y se define la función __new__ del Tipo. El tipo Noddy contiene un miembro de cadena llamado first , y este miembro de cadena se inicializa en una cadena vacía como esta:

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 método __new__ definido aquí, tendríamos que usar PyType_GenericNew , que simplemente inicializa todos los miembros de la variable de instancia en NULL. Entonces, el único beneficio del método __new__ es que la variable de instancia comenzará como una cadena vacía, en oposición a NULL. Pero ¿por qué es esto útil alguna vez, ya que si nos preocupamos de asegurarnos de que nuestras variables de instancia se inicialicen con algún valor predeterminado, podríamos haberlo hecho en el método __init__ ?


La diferencia surge principalmente con tipos mutables vs inmutables.

__new__ acepta un tipo como primer argumento, y (generalmente) devuelve una nueva instancia de ese tipo. Por lo tanto, es adecuado para su uso tanto con tipos mutables como inmutables.

__init__ acepta una instancia como primer argumento y modifica los atributos de esa instancia. Esto es inapropiado para un tipo inmutable, ya que les permitiría modificarse después de la creación llamando a obj.__init__(*args) .

Compare el comportamiento de la tuple y la 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]

En cuanto a por qué están separados (aparte de razones históricas simples): __new__ métodos requieren un montón de repetitivo para hacerlo bien (la creación inicial del objeto, y luego recordar devolver el objeto al final). __init__ métodos __init__ , por el contrario, son muy simples, ya que solo configura los atributos que necesita establecer.

Además de que los métodos __init__ son más fáciles de escribir y la distinción mutable vs inmutable mencionada anteriormente, la separación también se puede aprovechar para hacer que llamar a la clase padre __init__ en subclases sea opcional al configurar cualquier invariante de instancia absolutamente necesaria en __new__ . Sin embargo, esto generalmente es una práctica dudosa; por lo general, es más claro llamar a los métodos __init__ clase para padres según sea necesario.


No es una respuesta completa, pero tal vez algo que ilustra la diferencia.

__new__ siempre se llamará cuando se deba crear un objeto. Hay algunas situaciones en las que __init__ no se llamará. Un ejemplo es cuando deshace los objetos de un archivo pickle, se los asigna ( __new__ ) pero no se inicializan ( __init__ ).


Probablemente __new__ otros usos para __new__ pero hay uno realmente obvio: no puede subclasificar un tipo inmutable sin usar __new__ . Por ejemplo, supongamos que desea crear una subclase de tupla que solo contenga valores integrales entre 0 y 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)

Simplemente no puede hacer esto con __init__ - si intentó modificar self en __init__ , el intérprete se quejaría de que está tratando de modificar un objeto inmutable.


Solo quiero agregar una palabra sobre la intención (en oposición al comportamiento) de definir __new__ versus __init__ .

Me encontré con esta pregunta (entre otras) cuando intentaba entender la mejor manera de definir una fábrica de clases. Me di cuenta de que una de las formas en que __new__ es conceptualmente diferente de __init__ es el hecho de que el beneficio de __new__ es exactamente lo que se dijo en la pregunta:

Entonces, el único beneficio del nuevo método es que la variable de instancia comenzará como una cadena vacía, en oposición a NULL. Pero, ¿por qué es esto útil alguna vez, ya que si nos preocupamos de asegurarnos de que nuestras variables de instancia se inicialicen con algún valor predeterminado, podríamos haberlo hecho en el método init ?

Considerando el escenario indicado, 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, definiremos estas condiciones / propiedades en un método __new__ de una metaclase.

Estaba confundido acerca de esto hasta que realmente pensé en la aplicación del concepto más que en su significado. Aquí hay un ejemplo que con suerte haría la diferencia clara:

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

Tenga en cuenta que esto es solo un ejemplo demonstartive. Hay varias formas 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 cuestión de brevedad (por ejemplo, declarar explícitamente la metaclase) )

Si está creando una clase regular (también conocida como no metacásica), entonces __new__ realmente no tiene sentido a menos que sea un caso especial como el escenario mutable versus inmutable en la respuesta de ncoghlan (que es esencialmente un ejemplo más específico del concepto de definir los valores / propiedades iniciales de la clase / tipo que se está creando a través de __new__ para inicializarlos a través de __init__ ).


__new__() puede devolver objetos de tipos distintos de la clase a la que está vinculado. __init__() solo inicializa una instancia existente de la clase.

>>> class C(object): ... def __new__(cls): ... return 5 ... >>> c = C() >>> print type(c) <type ''int''> >>> print c 5