2011-02-01 19 views
93

La pregunta que yo estoy a punto de preguntarle parece ser un duplicado de Python's use of __new__ and __init__?, pero independientemente, aún no está claro para mí exactamente lo que la diferencia práctica entre __new__ y __init__ es.Python (y Python API C): __new__ frente __init__

Antes de salir corriendo a decirme que __new__ es para los objetos que crean y __init__ es para inicializar objetos, quiero ser claro: lo entiendo. De hecho, esa distinción es bastante natural para mí, ya que tengo experiencia en C++ donde tenemos placement new, que separa de manera similar la asignación de objetos de la inicialización.

El Python C API tutorial lo explica de esta manera:

El nuevo miembro es responsable de la creación de (a diferencia de inicialización) objetos del tipo. Está expuesto en Python como el método __new__(). ... Una razón para implementar un nuevo método es garantizar los valores iniciales de las variables de instancia .

Así que, sí - me consigo lo __new__ hace, pero a pesar de esto, todavía No entiendo por qué es útil en Python. El ejemplo dado dice que __new__ puede ser útil si quiere "asegurar los valores iniciales de las variables de instancia". Bueno, ¿no es eso exactamente lo que __init__ va a hacer?

En el tutorial 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 la cadena se inicializa a una cadena vacía, así:

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 utilizar PyType_GenericNew, que simplemente inicializa todos los miembros de la variable de instancia a 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__?

Respuesta

105

La diferencia se debe principalmente a tipos mutables e inmutables.

__new__ acepta 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 no es apropiado para un tipo inmutable, ya que les permitiría modificarlo después de la creación llamando al obj.__init__(*args).

comparar el comportamiento de tuple y 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 separadas (aparte de razones históricas simples): __new__ métodos requieren un montón de texto modelo para hacerlo bien (la creación del objeto inicial, y luego recordar devolver el objeto al final). __init__ métodos, por el contrario, son muy simples, ya que solo establece los atributos que necesita establecer.

Aparte de __init__ métodos que son más fáciles de escribir, y lo mutable inmutable vs distinción se ha indicado anteriormente, la separación también se puede aprovechar para hacer que llamar a la clase madre __init__ en las subclases opcionales mediante la creación de cualquier invariantes instancia absolutamente necesarios en __new__. Sin embargo, esto generalmente es una práctica dudosa; por lo general, es más claro llamar a los métodos de clase primaria __init__ según sea necesario.

+1

el código al que se refiere como "repetitivo" en '__new__' no es un texto repetitivo, porque el texto repetitivo nunca cambia. A veces necesitas reemplazar ese código en particular con algo diferente. –

+10

Crear, o adquirir de otra manera, la instancia (generalmente con una llamada 'super') y devolver la instancia son partes necesarias de cualquier implementación' __new__', y la 'repetición' a la que me refiero. Por el contrario, 'pass' es una implementación válida para' __init__' - no se requiere ningún tipo de comportamiento. – ncoghlan

24

__new__() pueden devolver objetos de tipos distintos a la clase a la que están destinados. __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 
+0

Esta es la explicación más escueta hasta el momento. – Tarik

+0

No es del todo cierto. Tengo métodos '__init__' que contienen código que se parece a' self .__ 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 'int' como lo hiciste ... Obtuve un error sobre los tipos de montón o algo así ... pero mi ejemplo de asignarlo a una clase creada dinámicamente funciona. – ArtOfWarfare

31

Probablemente haya otros usos para __new__ pero hay uno muy obvio: No se puede crear una subclase de 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 se puede hacer esto con __init__ - si se trató de modificar self en __init__, el intérprete se queja de que usted está tratando de modificar un objeto inmutable.

+1

No entiendo por qué deberíamos usar super? Quiero decir por qué debería __new__ devolver una instancia de la superclase? Además, como tú lo pones, ¿por qué deberíamos pasar cls explícitamente a __new__? super (ModularTuple, cls) no devuelve un método enlazado? – Alcott

+1

@Alcott, creo que estás malinterpretando el comportamiento de '__new__'. Pasamos 'cls' explícitamente a' __new__' porque, como puede leer [aquí] (http://docs.python.org/reference/datamodel.html#object.__new__) '__new__' _always_ requiere un tipo como primer argumento. Luego devuelve una instancia de ese tipo. Así que no estamos devolviendo una instancia de la superclase; estamos devolviendo una instancia de 'cls'. En este caso, es lo mismo que si hubiéramos dicho 'tuple .__ new __ (ModularTuple, tup)'. – senderle

11

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

__new__ siempre se llama cuando se debe crear un objeto. Hay algunas situaciones en las que no se llamará al __init__. Un ejemplo es cuando desenchufa objetos de un archivo pickle, se los asignará (__new__) pero no se inicializará (__init__).

+0

¿Llamaría a __init__ desde __new__ si quisiera que se asigne la memoria y se inicialicen los datos? ¿Por qué si __new__ no existe cuando se llama a la instancia de creación __init__? –

+1

El trabajo del método '__new__' es * crear * (esto implica 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 al que se enfrenta. –

0

Sólo quiero añadir una palabra sobre la intención (a diferencia de la conducta) de definir __new__ frente __init__.

Me encontré con esta pregunta (entre otras) cuando estaba tratando de 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 afirma en la pregunta: ¿

Así que la única ventaja 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, que se preocupan por los valores iniciales de las variables de instancia cuando la instancia es en realidad una clase en sí.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é acerca de la aplicación del concepto más que solo su significado. He aquí un ejemplo que es de esperar que 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 

Nota esto es sólo 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 frente a inmutable en ncoghlan's answer respuesta (que es esencialmente un ejemplo más específico del concepto de definición de los valores/propiedades iniciales de la clase/tipo que se está creando a través de __new__ para ser inicializado a través del __init__).

Cuestiones relacionadas