2009-03-23 10 views
422

Estoy tratando de simplificar una de mis clases y he introducido algunas funcionalidades en el mismo estilo que el flyweight design pattern.¿Por qué __init__() siempre se invoca después de __new __()?

Sin embargo, estoy un poco confundido sobre por qué __init__ siempre se llama después de __new__. No esperaba esto. ¿Puede alguien decirme por qué sucede esto y cómo puedo implementar esta funcionalidad? (. Además de poner la aplicación en el __new__ que se siente bastante hacky)

He aquí un ejemplo:

class A(object): 
    _dict = dict() 

    def __new__(cls): 
     if 'key' in A._dict: 
      print "EXISTS" 
      return A._dict['key'] 
     else: 
      print "NEW" 
      return super(A, cls).__new__(cls) 

    def __init__(self): 
     print "INIT" 
     A._dict['key'] = self 
     print "" 

a1 = A() 
a2 = A() 
a3 = A() 

Salidas:

NEW 
INIT 

EXISTS 
INIT 

EXISTS 
INIT 

¿Por qué?

+0

¿Qué pasa con esto? ¿Qué estás tratando de lograr que esto no ocurra todavía? Además, ¿por qué tener un ítem 1 ítem? –

Respuesta

443

Uso __new__ cuando se necesita para controlar la creación de una nueva instancia. Use __init__ cuando necesite controlar la inicialización de una nueva instancia.

__new__ es el primer paso en la creación de instancias. Se llama primero, y es responsable de devolver una nueva instancia de su clase. Por el contrario, __init__ no devuelve nada; solo es responsable de inicializar la instancia después de que se haya creado.

En general, no debería ser necesario para anulación __new__ a menos que esté subclasificación de un tipo inmutable como str, int, Unicode o tupla.

Desde: http://mail.python.org/pipermail/tutor/2008-April/061426.html

Debe tener en cuenta que lo que está tratando de hacer se hace generalmente con un Factory y esa es la mejor manera de hacerlo. El uso de __new__ no es una buena solución limpia, así que tenga en cuenta el uso de una fábrica.Aquí tienes a good factory example.

+12

La forma en que se explica ahora es un Singleton, no una fábrica. – vartec

+0

Terminó usando '__new__' dentro de una clase de fábrica, que se ha vuelto bastante limpia, así que gracias por su contribución. – Dan

+7

Lo siento, no estoy de acuerdo en que el uso de '__new__' se limite estrictamente a los casos indicados. Lo encontré muy útil para implementar fábricas de clases genéricas extensibles: vea [mi respuesta] (http://stackoverflow.com/a/28076300/355230) a la pregunta _ ¿Uso incorrecto de '__new__' para generar clases en Python? _ para un ejemplo de hacerlo. – martineau

137

__new__ es método de clase estática, mientras que __init__ es método de instancia. __new__ tiene que crear la instancia primero, por lo que __init__ puede inicializarlo. Tenga en cuenta que __init__ toma self como parámetro. Hasta que cree una instancia, no hay self.

Supongo que está intentando implementar singleton pattern en Python. Hay algunas maneras de hacer eso.

Además, a partir de Python 2.6, puede utilizar la clase decorators.

def singleton(cls): 
    instances = {} 
    def getinstance(): 
     if cls not in instances: 
      instances[cls] = cls() 
     return instances[cls] 
    return getinstance 

@singleton 
class MyClass: 
    ... 
+6

@Tyler Long, no entiendo muy bien cómo funciona el "@singleton". Porque el decorador devuelve una función, pero MyClass es una clase. – Alcott

+8

¿Por qué el diccionario? Como cls siempre será el mismo y obtendrá un diccionario nuevo, por ejemplo para cada singleton, está creando un diccionario con solo un elemento. –

+7

@Alcott: No se necesita opinión - [los documentos] (http://docs.python.org/reference/datamodel.html?highlight=__new__#object.__new__) están de acuerdo con usted. –

-1

Sin embargo, estoy un poco confundido sobre por qué __init__ siempre se llama después de __new__.

No es mucho más que solo que se hace de esa manera. __new__ no tiene la responsabilidad de inicializar la clase, algún otro método lo hace (__call__, posiblemente-- No estoy seguro).

No esperaba esto. ¿Puede alguien decirme por qué sucede esto y cómo implemento esta funcionalidad? (aparte de poner la implementación en el __new__ que se siente bastante hacky).

Usted podría tener __init__ hacer nada si ya se ha inicializado, o se podría escribir un nuevo metaclase con un nuevo __call__ que sólo llama __init__ de nuevos casos, y por lo demás sólo devuelve __new__(...).

3

__new__ debe devolver una nueva instancia en blanco de una clase. __init__ se llama para inicializar esa instancia. No está llamando a __init__ en el caso "NUEVO" de __new__, por lo que se lo está llamando a usted. El código que está llamando al __new__ no realiza un seguimiento de si __init__ ha sido llamado en una instancia particular o no, o no, porque está haciendo algo muy inusual aquí.

Puede agregar un atributo al objeto en la función __init__ para indicar que se ha inicializado. Verifique la existencia de ese atributo como lo primero en __init__ y no avance más si lo ha sido.

18

citar el documentation:

implementaciones típicas crear una nueva instancia de la clase mediante la invocación de el método __new __ de la superclase() usando "super (currentclass, CLS) .__ nueva __ (CLS [.. .]) "con argumentos apropiados y luego modificando la instancia recién creada según sea necesario antes de devolverla.

...

Si __new __() no devuelve una instancia de cls, entonces no será invoca el método nuevo de ejemplo __init __().

__new __() está destinado principalmente a permitir subclases de tipos inmutables (como int, str o tuple) para personalizar la creación de instancias.

+11

'Si __new __() no devuelve una instancia de cls, entonces no se invocará el método __init __() de la nueva instancia. Ese es un punto importante.Si devuelve una instancia diferente, nunca se invoca '__init__' original. –

122

En la mayoría de los lenguajes OO conocidos, una expresión como SomeClass(arg1, arg2) asignará una nueva instancia, inicializará los atributos de la instancia y luego la devolverá.

En lenguajes orientados a objetos más conocidos, la parte "inicializar atributos de la instancia" se pueden personalizar para cada clase mediante la definición de un constructor, que es básicamente un bloque de código que funciona en la nueva instancia (utilizando el argumentos proporcionados a la expresión del constructor) para configurar las condiciones iniciales deseadas. En Python, esto corresponde al método de clase '__init__.

Python's __new__ es nada más y nada menos que la personalización por clase similar de la parte "asignar una nueva instancia". Esto, por supuesto, le permite hacer cosas inusuales, como devolver una instancia existente en lugar de asignar una nueva. Entonces, en Python, realmente no deberíamos pensar que esta parte necesariamente involucre asignación; todo lo que necesitamos es que __new__ encuentre una instancia adecuada de algún lado.

Pero todavía es solo la mitad del trabajo, y no hay forma de que el sistema Python sepa que a veces desea ejecutar la otra mitad del trabajo (__init__) posteriormente y otras veces no. Si quieres ese comportamiento, tienes que decirlo explícitamente.

A menudo, puede refactorizar lo que sólo necesita __new__, o por lo que no necesita __new__, o para que __init__ comporta de manera diferente en un objeto inicializado ya. Pero si realmente lo desea, Python realmente le permite redefinir "el trabajo", de modo que SomeClass(arg1, arg2) no necesariamente llame al __new__ seguido de __init__. Para hacer esto, debe crear una metaclase y definir su método __call__.

Una metaclase es solo la clase de una clase. Y el método de clase __call__ controla lo que ocurre cuando llamas a las instancias de la clase. Entonces, un método de metaclass '__call__ controla lo que ocurre cuando se llama a una clase; es decir, le permite redefinir el mecanismo de creación de instancias de principio a fin. Este es el nivel en el que puede implementar de forma más elegante un proceso de creación de instancia completamente no estándar, como el patrón singleton. De hecho, con menos de 10 líneas de código se puede implementar una metaclase Singleton que entonces ni siquiera requiere que Futz con __new__en absoluto, y puede convertir cualquier clase de otro modo normal en un producto único, simplemente añadiendo __metaclass__ = Singleton !

class Singleton(type): 
    def __init__(self, *args, **kwargs): 
     super(Singleton, self).__init__(*args, **kwargs) 
     self.__instance = None 
    def __call__(self, *args, **kwargs): 
     if self.__instance is None: 
      self.__instance = super(Singleton, self).__call__(*args, **kwargs) 
     return self.__instance 

Sin embargo, esta es probablemente una magia más profunda de lo que realmente se justifica en esta situación.

1

Se llama al __init__ después de __new__ de modo que cuando lo sobrescriba en una subclase, su código agregado seguirá recibiendo llamadas.

Si usted está tratando de subclase de una clase que ya tiene un __new__, alguien inconsciente de esto podría empezar por la adaptación de la __init__ y desviar la llamada a la subclase __init__. Esta convención de llamar al __init__ después de __new__ ayuda a que funcione como se espera.

El __init__ aún necesita tener en cuenta los parámetros que necesita la superclase __new__, pero si no lo hace, normalmente creará un claro error de tiempo de ejecución. Y el __new__ probablemente debería permitir explícitamente *args y '** kw', para dejar en claro que la extensión es correcta.

En general, es malo tener __new__ y __init__ en la misma clase en el mismo nivel de herencia, debido al comportamiento descrito en el póster original.

10

Me doy cuenta de que esta pregunta es bastante antigua pero tuve un problema similar. El siguiente hizo lo que quería:

class Agent(object): 
    _agents = dict() 

    def __new__(cls, *p): 
     number = p[0] 
     if not number in cls._agents: 
      cls._agents[number] = object.__new__(cls) 
     return cls._agents[number] 

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

    def __eq__(self, rhs): 
     return self.number == rhs.number 

Agent("a") is Agent("a") == True 

que utilizan esta página como un recurso http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html

+2

Nota: \ _ \ _ new \ _ \ _ siempre devuelve un objeto apropiado, por lo que \ _ \ _ init \ _ \ _ siempre se llama, incluso si la instancia ya existe. – Oddthinking

8

Creo que la respuesta simple a esta pregunta es que, si __new__ devuelve un valor que es el mismo tipo que el clase, la función __init__ se ejecuta, de lo contrario no lo hará. En este caso, su código devuelve A._dict('key') que es la misma clase que cls, por lo que se ejecutará __init__.

2

Referring to this doc:

Cuando la subclasificación inmutable tipos incorporados como números y cadenas, y ocasionalmente en otras situaciones, el método estático nueva viene útil. nuevo es el primer paso en la construcción de instancia, invocó antes de init.

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

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

hay situaciones donde se crea una nueva instancia sin llamar init (por ejemplo cuando la instancia se carga desde un pepinillo). No hay forma de crear una nueva instancia sin llamar a nuevo (aunque en algunos casos puede salirse con la llamada de una clase base nueva).

En cuanto a lo que se desea lograr, también en el mismo documento de información de aproximadamente patrón Singleton

class Singleton(object): 
     def __new__(cls, *args, **kwds): 
      it = cls.__dict__.get("__it__") 
      if it is not None: 
       return it 
      cls.__it__ = it = object.__new__(cls) 
      it.init(*args, **kwds) 
      return it 
     def init(self, *args, **kwds): 
      pass 

también puede utilizar esta implementación del PEP 318, usando un decorador

def singleton(cls): 
    instances = {} 
    def getinstance(): 
     if cls not in instances: 
      instances[cls] = cls() 
     return instances[cls] 
    return getinstance 

@singleton 
class MyClass: 
... 
+0

Llamar a una forma bastarda de 'init' de' __new__' suena realmente hacky. Esto es para lo que son las metaclases. –

3

Uno debería mirar init como un simple constructor en los lenguajes OO tradicionales. Por ejemplo, si está familiarizado con Java o C++, al constructor se le pasa implícitamente un puntero a su propia instancia. En el caso de Java, es la variable "this". Si uno fuera a inspeccionar el código de bytes generado para Java, uno notaría dos llamadas. La primera llamada es a un método "nuevo", y luego la siguiente llamada es al método init (que es la llamada real al constructor definido por el usuario). Este proceso de dos pasos permite la creación de la instancia real antes de llamar al método constructor de la clase, que es solo otro método de esa instancia.

Ahora, en el caso de Python, nuevo es una instalación adicional que es accesible para el usuario. Java no proporciona esa flexibilidad, debido a su naturaleza tipada. Si un lenguaje proporcionó esa facilidad, el implementador de new podría hacer muchas cosas en ese método antes de devolver la instancia, incluso la creación de una instancia totalmente nueva de un objeto no relacionado en algunos casos. Y este enfoque también funciona bien especialmente para tipos inmutables en el caso de Python.

8

Cuando __new__ devuelve una instancia de la misma clase, __init__ se ejecuta posteriormente en el objeto devuelto. Es decir. NO puede usar __new__ para evitar que se ejecute __init__. Incluso si devuelve un objeto previamente creado desde __new__, será doble (triple, etc ...) inicializado por __init__ una y otra vez.

Aquí es el enfoque genérico para el patrón Singleton que se extiende por encima de respuesta VarTec y lo fija:

def SingletonClass(cls): 
    class Single(cls): 
     __doc__ = cls.__doc__ 
     _initialized = False 
     _instance = None 

     def __new__(cls, *args, **kwargs): 
      if not cls._instance: 
       cls._instance = super(Single, cls).__new__(cls, *args, **kwargs) 
      return cls._instance 

     def __init__(self, *args, **kwargs): 
      if self._initialized: 
       return 
      super(Single, self).__init__(*args, **kwargs) 
      self.__class__._initialized = True # Its crucial to set this variable on the class! 
    return Single 

historia completa es here.

Otro enfoque, que de hecho implica __new__ es utilizar classmethods:

class Singleton(object): 
    __initialized = False 

    def __new__(cls, *args, **kwargs): 
     if not cls.__initialized: 
      cls.__init__(*args, **kwargs) 
      cls.__initialized = True 
     return cls 


class MyClass(Singleton): 
    @classmethod 
    def __init__(cls, x, y): 
     print "init is here" 

    @classmethod 
    def do(cls): 
     print "doing stuff" 

preste atención, que con este enfoque lo necesario para decorar TODOS sus métodos con @classmethod, porque nunca se va a utilizar cualquier instancia real de MyClass.

4
class M(type): 
    _dict = {} 

    def __call__(cls, key): 
     if key in cls._dict: 
      print 'EXISTS' 
      return cls._dict[key] 
     else: 
      print 'NEW' 
      instance = super(M, cls).__call__(key) 
      cls._dict[key] = instance 
      return instance 

class A(object): 
    __metaclass__ = M 

    def __init__(self, key): 
     print 'INIT' 
     self.key = key 
     print 

a1 = A('aaa') 
a2 = A('bbb') 
a3 = A('aaa') 

salidas:

NEW 
INIT 

NEW 
INIT 

EXISTS 

NB Como efecto secundario M._dict propiedad se convierte automáticamente accesible desde A como A._dict así que tenga cuidado de no sobrescribir que dicho sea de paso.

+0

Falta un método '__init__' que establece' cls._dict = {} '. Probablemente no desee que todas las clases de este metatipo compartan un diccionario (pero +1 para la idea). –

3

¡Excavar un poco más profundo en eso!

El tipo de una clase genérica en CPython es type y su clase base es Object (a menos que defina explícitamente otra clase base como una metaclase). La secuencia de llamadas de bajo nivel se puede encontrar en here. El primer método llamado es el type_call que luego llama al tp_new y luego al tp_init.

La parte interesante aquí es que tp_new llamará a los Object 's (clase base) nuevo método object_new que hace un tp_alloc (PyType_GenericAlloc), que asigna la memoria para el objeto :)

En ese momento el objeto es creado en la memoria y luego se llama al método __init__. Si __init__ no está implementado en su clase, entonces la object_init es llamado y no hace nada :)

Entonces type_call simplemente devuelve el objeto que se une a la variable.

2

Una respuesta a @AntonyHatchkins, probablemente desee un diccionario de instancias para cada clase del metatipo, lo que significa que debe tener un método __init__ en la metaclase para inicializar su objeto de clase con ese diccionario en lugar de hacerlo global en todas las clases

class MetaQuasiSingleton(type): 
    def __init__(cls, name, bases, attibutes): 
     cls._dict = {} 

    def __call__(cls, key): 
     if key in cls._dict: 
      print('EXISTS') 
      instance = cls._dict[key] 
     else: 
      print('NEW') 
      instance = super().__call__(key) 
      cls._dict[key] = instance 
     return instance 

class A(metaclass=MetaQuasiSingleton): 
    def __init__(self, key): 
     print 'INIT' 
     self.key = key 
     print() 

he ido por delante y actualizada del código original con un método __init__ y ha cambiado la sintaxis de Python (llamada sin argumentos a super y metaclase en los argumentos de clase en lugar de como un atributo) 3 notación.

De cualquier manera, el punto importante aquí es que su inicializador de clase (método __call__) no ejecutará __new__ o __init__ si se encuentra la clave. Esto es mucho más limpio que usar __new__, que requiere que marque el objeto si desea omitir el paso predeterminado __init__.

Cuestiones relacionadas