2011-12-26 22 views
8

Estoy tratando de comprender cómo deberían crearse nuevas instancias de una clase Python cuando el proceso de creación puede ser mediante el constructor o mediante el método __new__. En particular, observo que cuando se utiliza el constructor, el método __init__ se llamará automáticamente después de __new__, mientras que al invocar __new__ directamente no se llamará automáticamente a la clase __init__. Puedo forzar que se llame a __init__ cuando se llama explícitamente __new__ incrustando una llamada al __init__ dentro de __new__, pero luego __init__ terminará recibiendo llamadas dos veces cuando la clase se crea mediante el constructor.Asegurando que __init__ solo se invoque una vez cuando el constructor crea una instancia de clase o __new__

Por ejemplo, considere la siguiente clase de juguete, que almacena una propiedad interna, es decir, un objeto list llamado data: es útil considerarlo como el inicio de una clase vectorial.

class MyClass(object): 
    def __new__(cls, *args, **kwargs): 
     obj = object.__new__(cls, *args, **kwargs) 
     obj.__init__(*args, **kwargs) 
     return obj 

    def __init__(self, data): 
     self.data = data 

    def __getitem__(self, index): 
     return self.__new__(type(self), self.data[index]) 

    def __repr__(self): 
     return repr(self.data) 

una nueva instancia de la clase se pueden crear utilizando el constructor (en realidad no sé si eso es la terminología correcta en Python), algo así como

x = MyClass(range(10))

o mediante corte en rodajas, la cual se puede ver invoca una llamada al __new__ en el método __getitem__.

x2 = x[0:2]

En el primer caso, __init__ se llamará dos veces (tanto a través de la llamada explícita dentro de __new__ y luego de nuevo automáticamente), y una vez en el segundo ejemplo. Obviamente, solo me gustaría llamar al __init__ una vez en cualquier caso. ¿Hay una forma estándar de hacer esto en Python?

Tenga en cuenta que en mi ejemplo que podía deshacerse del método __new__ y redefinir __getitem__ como

def __getitem__(self, index): 
    return MyClass(self.data[index]) 

pero entonces esto podría causar un problema si más adelante quiero heredar de MyClass, porque si hago una llamada como child_instance[0:2], obtendré una instancia de MyClass, no la clase secundaria.

Respuesta

8

En primer lugar, algunos datos básicos sobre __new__ y __init__:

  • __new__ es un constructor.
  • __new__ normalmente devuelve una instancia de cls, su primer argumento.
  • Por __new__ devolviendo una instancia de cls, __new__ causes Python to call __init__.
  • __init__ es un inicializador . Modifica la instancia (self) devuelta por __new__. No necesita devolver self.

Cuando MyClass define:

def __new__(cls, *args, **kwargs): 
    obj = object.__new__(cls, *args, **kwargs) 
    obj.__init__(*args, **kwargs) 
    return obj 

MyClass.__init__ es llamado dos veces. Una vez desde llamar al obj.__init__ explícitamente, y una segunda vez porque __new__ devolvió obj, una instancia de cls. (Desde el primer argumento de object.__new__ es cls, la instancia devuelta es una instancia de MyClass por lo obj.__init__ llamadas MyClass.__init__, no object.__init__.)


El Python 2.2.3 release notes tiene un comentario interesante, que arroja luz sobre cuándo utilizar __new__ y cuándo utilizar __init__:

el método __new__ se llama con la clase como su primer argumento; su responsabilidad es devolver una nueva instancia de esa clase.

Comparar esto con __init__: __init__ se llama con una instancia como su primer argumento , y no devuelve nada; su responsabilidad es para inicializar la instancia.

Todo esto se hace para que los tipos inmutables puedan conservar su inmutabilidad al tiempo que permiten la subclasificación.

Los tipos inmutables (int, long, float, complejo, str, Unicode y tupla) tienen un maniquí __init__, mientras que los tipos mutable (dict, lista, de archivos, y también estupendo, classmethod, métodoestático, y propiedad) tienen un dummy __new__.

lo tanto, utilizar __new__ para definir tipos inmutables, y utilizar __init__ para definir tipos mutables. Si bien es posible definir ambos, no debería necesitar hacerlo.


Por lo tanto, ya que MyClass es mutable, sólo se debe definir __init__:

class MyClass(object): 
    def __init__(self, data): 
     self.data = data 

    def __getitem__(self, index): 
     return type(self)(self.data[index]) 

    def __repr__(self): 
     return repr(self.data) 

x = MyClass(range(10)) 
x2 = x[0:2] 
+0

Gracias por la descripción detallada @unutbu. – Abiel

+1

Esto es engañoso. Primero, '__new__' no llama a' __init__' y luego devuelve la instancia. '__new__' se llama para crear una instancia, luego' __init__' se invoca en lo que '__new__' devolvió. Esta es la razón por la cual invocar '__new__' directamente no llama a' __init__'. En segundo lugar, '__new__' no se parece en nada a un constructor, y no debería llamarse uno. '__init__' se llama constructor en los documentos oficiales de Python. – Ben

+0

@Ben, ¿tiene una referencia de los documentos de python? Parece que aclararía un montón de debate sobre si '__init__' es un constructor ... https://stackoverflow.com/questions/6578487/init-as-a-constructor – pylang

1

Hay un par de cosas que no se debe hacer:

  • llamada __init__ de __new__
  • __new__ llamada directamente en un método

Como ya hemos visto, tanto el __new__ y los métodos __init__ se invocan automáticamente al crear un objeto de una clase determinada. Usarlos directamente rompería esta funcionalidad (llamando a __init__ dentro de otra __init__ está permitido, como se puede ver en el ejemplo a continuación).

Puede obtener la clase del objeto en cualquier método de conseguir el atributo __class__ como en el siguiente ejemplo:

class MyClass(object): 
    def __new__(cls, *args, **kwargs): 
     # Customized __new__ implementation here 
     return obj 

    def __init__(self, data): 
     super(MyClass, self).__init__(self) 
     self.data = data 

    def __getitem__(self, index): 
     cls = self.__class__ 
     return cls(self.data[index]) 

    def __repr__(self): 
     return repr(self.data) 

x = MyClass(range(10)) 
x2 = x[0:2] 
+0

¿Cuál es el propósito de '__new __()' en la clase como se muestra en su ejemplo? – martineau

+0

@martineau Gracias por su comentario. La implementación '__new__' en el ejemplo estaba allí como un marcador de posición para cualquier implementación deseada. De todos modos, como señaló @unutbu en su respuesta, es cierto que no es realmente necesario para esta pregunta en particular, por lo que he actualizado mi respuesta para evitar confusiones. – jcollado

0

Cuando se crea una instancia de una clase con MyClass(args), la secuencia de creación de la instancia predeterminada es la siguiente:

  1. MyClass.__new__(args) se invoca para obtener una nueva instancia "en blanco"
  2. new_instance.__init__(args) se invoca (new_instance es la instancia de regresar de la llamada a __new__ como anteriormente) para inicializar los atributos de la nueva instancia [1]
  3. new_instance se devuelve como el resultado de MyClass(args)

partir de esto, que es claro para ver que llamando MyClass.__new__ usted mismo no resultado en __init__ se llama, por lo que terminará con una instancia no inicializada. También es evidente que realizar una llamada al __init__ en __new__ tampoco será correcto, ya que entonces MyClass(args) llamará al MyClass(args) al __init__dos veces.

El origen del problema es la siguiente:

Estoy tratando de entender cómo las nuevas instancias de una clase Python debe creado cuando el proceso de creación puede ser o bien a través del constructor o a través de la nuevo método

El proceso de creación no debería ser normalmente a través del método __new__. __new__ es parte del protocolo de creación de instancia normal, por lo que no debe esperar que invoque todo el protocolo por usted.

Una (mala) solución sería implementar este protocolo a mano usted mismo; en lugar de:

def __getitem__(self, index): 
    return self.__new__(type(self), self.data[index]) 

usted podría tener:

def __getitem__(self, index): 
    new_item = self.__new__(type(self), self.data[index]) 
    new_item.__init__(self.data[index]) 
    return new_item 

Pero en realidad, lo que quiere hacer es no meterse con __new__ en absoluto. El valor predeterminado __new__ está bien para su caso, y el protocolo de creación de instancias predeterminado está bien para su caso, por lo que no debe implementar __new__ ni llamarlo directamente.

Lo que desea es crear una nueva instancia de la clase de la manera normal, llamando a la clase. Si no hay herencia en curso y no cree que vaya a haber, simplemente reemplace self.__new__(type(self), self.data[index]) con MyClass(self.data[index]).

Si usted piensa que podría ser un día subclases de MyClass que desee crear instancias de la subclase través de rebanar en lugar de MyClass, entonces usted necesita para obtener dinámicamente la clase de self e invocar eso. ¡Ya sabes cómo hacerlo, porque lo usaste en tu programa!type(self) devolverá el tipo (clase) de self, que luego puede invocar exactamente como lo invocaría directamente a través de MyClass: type(self)(self.data[index]).


Como acotación al margen, el punto de __new__ es cuando se desea personalizar el proceso de obtención de una "nueva" instancia de una clase en blanco antes de que se inicializa. Casi todo el tiempo, esto es completamente innecesario y el valor predeterminado __new__ está bien.

Sólo es necesario __new__ en dos circunstancias:

  1. que estás han un esquema inusual "asignación", en la que podría devolver una instancia existente en lugar de crear una genuinamente nueva (la única manera de crear realidad una nueva instancia es delegar a la última implementación predeterminada de __new__ de todos modos).
  2. Está implementando una subclase de un tipo incorporado inmutable. Dado que los tipos incorporados inmutables no se pueden modificar después de la creación (porque son inmutables), se deben inicializar como en vez de crearlos en __init__.

Como una generalización del punto (1), se puede hacer __new__ de retorno lo que quiera (no necesariamente una instancia de la clase) para hacer la invocación de una clase se comportan de alguna manera arbitraria extraña. Sin embargo, parece que casi siempre sería más confuso que útil.


[1] Creo que el protocolo es un poco más complejo; __init__ solo se invoca en el valor devuelto por __new__ si se trata de una instancia de la clase invocada para iniciar el proceso. Sin embargo, es muy raro que este no sea el caso.

Cuestiones relacionadas