¿Por qué siempre se llama a __init __ () después de __new __ ()?

568

Solo estoy tratando de simplificar una de mis clases y he introducido algunas funcionalidades en el mismo estilo que el patrón de diseño de peso mosca .

Sin embargo, estoy un poco confundido sobre por qué __init__siempre se llama después __new__. No esperaba esto. ¿Alguien puede decirme por qué sucede esto y cómo puedo implementar esta funcionalidad de otra manera? (Además de poner la implementación en lo __new__que parece bastante hacky).

Aquí hay 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é?

Dan
fuente
estaba tratando de entender el patrón de diseño también, y escuché por primera vez sobre: ​​patrón de diseño de peso mosca ... y muy buen enlace con ejemplo en casi todos los idiomas populares.
EmmaYang

Respuestas:

598

Úselo __new__cuando necesite controlar la creación de una nueva instancia.

Úselo __init__cuando necesite controlar la inicialización de una nueva instancia.

__new__es el primer paso de 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 necesitar anular a __new__menos que esté subclasificando un tipo inmutable como str, int, unicode o tuple.

Desde la publicación de abril de 2008: ¿ Cuándo usar __new__vs. __init__? en mail.python.org.

Debe considerar que lo que está tratando de hacer generalmente se hace con una Fábrica y esa es la mejor manera de hacerlo. El uso __new__no es una buena solución limpia, así que considere el uso de una fábrica. Aquí tienes un buen ejemplo de fábrica .

mpeterson
fuente
1
Terminé usando __new__dentro de una clase Factory, que se ha vuelto bastante limpia, así que gracias por su aporte.
Dan
11
Lo siento, no estoy de acuerdo con que el uso de __new__se limite estrictamente a los casos indicados. Lo he encontrado muy útil para implementar fábricas de clases genéricas extensibles. Vea mi respuesta a la pregunta ¿ Uso inadecuado de __new__para generar clases en Python? para un ejemplo de hacerlo.
Martineau
171

__new__es un método de clase estático, mientras que __init__es un método de instancia. __new__primero tiene que crear la instancia, por lo que __init__puede inicializarla. Tenga en cuenta que __init__toma selfcomo parámetro. Hasta que cree una instancia no hay self.

Ahora, supongo, que estás tratando de implementar un patrón singleton en Python. Hay algunas formas de hacerlo.

Además, a partir de Python 2.6, puede usar decoradores de clase .

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

@singleton
class MyClass:
  ...
vartec
fuente
8
@Tyler Long, no entiendo cómo funciona el "@singleton". Porque el decorador devuelve una función pero MyClass es una clase.
Alcott
11
¿Por qué el diccionario? Como cls siempre será el mismo y obtienes un diccionario nuevo, por ejemplo, para cada singleton, estás creando un diccionario con solo un elemento.
Winston Ewert
77
@Alcott: No se necesita opinión, los documentos están de acuerdo con usted.
Ethan Furman
2
@Alcott. Sí, el decorador devuelve una función. pero tanto la clase como la función son invocables. Creo que instancias = {} debería ser una variable global.
Tyler Long
44
@TylerLong Alcott tiene un buen punto. Esto da como resultado que el nombre MyClassesté vinculado a una función que devuelve instancias de la clase original. Pero ahora no hay forma de referirse a la clase original, y al MyClassser una función se rompe isinstance/ issubclassverifica, el acceso a los atributos / métodos de MyClass.somethingla clase directamente como , nombrar la clase en superllamadas, etc., etc. Si eso es un problema o no, depende de clase a la que lo estás aplicando (y al resto del programa).
Ben
149

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 la mayoría de los lenguajes OO conocidos, la parte "inicializar los atributos de la instancia" se puede personalizar para cada clase definiendo un constructor , que es básicamente un bloque de código que opera en la nueva instancia (usando los argumentos proporcionados a la expresión del constructor ) para configurar las condiciones iniciales deseadas. En Python, esto corresponde al __init__método de la clase .

Python __new__es nada más y nada menos que una personalización similar por clase de la parte "asignar una nueva instancia". Por supuesto, esto le permite hacer cosas inusuales, como devolver una instancia existente en lugar de asignar una nueva. Entonces, en Python, no deberíamos pensar realmente en esta parte como necesariamente una asignación; todo lo que requerimos es que __new__aparezca 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__) después y otras veces no. Si quieres ese comportamiento, tienes que decirlo explícitamente.

A menudo, puede refactorizar para que solo lo necesite __new__, o para que no lo necesite __new__, o para que se __init__comporte de manera diferente en un objeto ya inicializado. Pero si realmente quieres, Python realmente te permite redefinir "el trabajo", por lo que eso SomeClass(arg1, arg2)no necesariamente llama __new__seguido de __init__. Para hacer esto, debe crear una metaclase y definir su __call__método.

Una metaclase es solo la clase de una clase. Y el __call__método de una clase controla lo que sucede cuando se llaman instancias de la clase. Entonces , el método de una metaclase__call__ controla lo que sucede 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 manera más elegante un proceso de creación de instancias completamente no estándar como el patrón singleton. De hecho, con menos de 10 líneas de código puede implementar una Singletonmetaclase que entonces ni siquiera requieren que futz con __new__ nada , y puede convertir cualquier clase de otra manera 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 para esta situación!

Ben
fuente
25

Para citar la documentación :

Las implementaciones típicas crean una nueva instancia de la clase invocando el método __new __ () de la superclase usando "super (currentclass, cls) .__ new __ (cls [, ...])" con los argumentos apropiados y luego modificando la instancia recién creada según sea necesario antes de devolverlo.

...

Si __new __ () no devuelve una instancia de cls, no se invocará el método __init __ () de la nueva instancia.

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

tgray
fuente
18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Ese es un punto importante. Si devuelve una instancia diferente, __init__nunca se llama al original .
Jeffrey Jose el
@tgray Me doy cuenta de que su respuesta es de los documentos, pero tengo curiosidad si conoce algún caso de uso de no devolver una instancia de cls. Parece que cuando el objeto devuelto de __new__se verifica para su clase, el método arrojaría un error en lugar de permitir que la verificación fallida pase silenciosamente, porque no entiendo por qué querría devolver algo más que un objeto de la clase .
soporific312
@ soporific312 No he visto ningún caso de uso. Esta respuesta analiza algunos de los razonamientos para el diseño, aunque tampoco han visto ningún código que aproveche esa "característica".
tgray
13

Me doy cuenta de que esta pregunta es bastante antigua, pero tuve un problema similar. Lo 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

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

Alexey
fuente
77
Nota: __new__ siempre devuelve un objeto apropiado, por lo que siempre se llama a __init__, incluso si la instancia ya existe.
Pensamiento extraño
10

Creo que la respuesta simple a esta pregunta es que, si __new__devuelve un valor que es del mismo tipo que la clase, la __init__función 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 __init__lo que se ejecutará.

PMN
fuente
10

Cuando __new__devuelve la instancia de la misma clase, __init__se ejecuta después en el objeto devuelto. Es decir, NO se puede usar __new__para evitar que __init__se ejecute. Incluso si devuelve un objeto creado previamente __new__, será doble (triple, etc.) inicializado __init__una y otra vez.

Aquí está el enfoque genérico del patrón Singleton que extiende la respuesta vartec anterior y la corrige:

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

La historia completa está aquí .

Otro enfoque, que de hecho implica __new__es usar métodos de clase:

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"

Por favor, preste atención, con este enfoque necesita decorar TODOS sus métodos @classmethod, porque nunca usará ninguna instancia real de MyClass.

Zaar Hai
fuente
6

En referencia a este documento :

Al subclasificar tipos incorporados inmutables como números y cadenas, y ocasionalmente en otras situaciones, el método estático nuevo es útil. new es el primer paso en la construcción de instancias, invocado antes de init .

Se llama al nuevo método con la clase como 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 en las que se crea una nueva instancia sin llamar a init (por ejemplo, cuando la instancia se carga desde un pickle). No hay forma de crear una nueva instancia sin llamar a new (aunque en algunos casos puede salirse con la llamada de una clase base nueva ).

En cuanto a lo que desea lograr, también hay información en el mismo documento sobre el 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 usar esta implementación desde 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:
...
Kiriloff
fuente
1
Llamar a una forma bastarda de initde __new__sonidos realmente HACKY. Para eso están las metaclases.
Físico loco
6
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

Nota: como una M._dictpropiedad de efectos secundarios se vuelve accesible automáticamente A, A._dictasí que tenga cuidado de no sobrescribirla de forma incidental.

Antony Hatchkins
fuente
Te falta un __init__método que establece cls._dict = {}. Probablemente no desee un diccionario compartido por todas las clases de este metatipo (pero +1 para la idea).
Mad Physicist
5

__new__ debería devolver una nueva instancia en blanco de una clase. Luego se llama a __init__ para inicializar esa instancia. No está llamando a __init__ en el caso "NUEVO" de __new__, por lo que se llama por usted. El código que está llamando __new__no realiza un seguimiento de si __init__ ha sido llamado en una instancia particular o no, o no debería hacerlo, 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 continúe si lo ha sido.

Andrew Wilkinson
fuente
5

Una actualización de la respuesta @AntonyHatchkins, probablemente desee un diccionario de instancias por separado para cada clase del metatipo, lo que significa que debe tener un __init__método 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()

Continué y actualicé el código original con un __init__método y cambié la sintaxis a la notación de Python 3 (llamada sin superargumentos y metaclase en los argumentos de la clase en lugar de como un atributo).

De cualquier manera, el punto importante aquí es que su inicializador de clase ( __call__método) tampoco se ejecutará __new__o __init__si se encuentra la clave. Esto es mucho más limpio que usarlo __new__, lo que requiere que marque el objeto si desea omitir el __init__paso predeterminado .

Físico loco
fuente
4

¡Profundizando un poco más en eso!

El tipo de una clase genérica en CPython es typey 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 aquí . El primer método llamado es el type_callque luego llama tp_newy luego tp_init.

La parte interesante aquí es que tp_newllamará al Objectnuevo método 's (clase base) object_newque hace un tp_alloc( PyType_GenericAlloc) que asigna la memoria para el objeto :)

En ese punto, el objeto se crea en la memoria y luego __init__se llama al método. Si __init__no está implementado en su clase, object_initse llama y no hace nada :)

Luego type_callsolo devuelve el objeto que se une a su variable.

xpapazaf
fuente
4

Uno debería verlo __init__como un simple constructor en 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 thisvariable. Si se inspeccionara el código de bytes generado para Java, se notarían dos llamadas. La primera llamada es a un método "nuevo", y luego la siguiente 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, __new__es una instalación adicional que es accesible para el usuario. Java no proporciona esa flexibilidad, debido a su naturaleza tipificada. Si un lenguaje proporcionara esa facilidad, entonces el implementador de __new__podría hacer muchas cosas en ese método antes de devolver la instancia, incluida 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.

grdvnl
fuente
4

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

Creo que la analogía de C ++ sería útil aquí:

  1. __new__simplemente asigna memoria para el objeto. Las variables de instancia de un objeto necesitan memoria para contenerlo, y esto es lo __new__que haría el paso .

  2. __init__ Inicializa las variables internas del objeto a valores específicos (podría ser por defecto).

HAltos
fuente
2

Se __init__invoca después __new__para que cuando lo anule en una subclase, su código agregado aún se invoque.

Si está intentando subclasificar una clase que ya tiene una __new__, alguien que no lo sepa puede comenzar adaptando __init__y reenviando la llamada a la subclase __init__. Esta convención de llamar __init__después __new__ayuda a que funcione como se esperaba.

El __init__todavía tiene que permitir cualquier parámetro de la superclase __new__es necesario, pero si no lo hace generalmente crear un error de ejecución clara. Y __new__probablemente debería permitir explícitamente *argsy '** kw', para dejar en claro que la extensión está bien.

Generalmente es una mala forma tener ambos __new__y __init__en la misma clase al mismo nivel de herencia, debido al comportamiento que describió el póster original.

Jay Obermark
fuente
1

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

No hay otra razón más que eso, simplemente se hace de esa manera. __new__no tiene la responsabilidad de inicializar la clase, algún otro método sí ( __call__, posiblemente, no estoy seguro).

No esperaba esto. ¿Alguien puede decirme por qué sucede esto y cómo implemento esta funcionalidad de otra manera? (aparte de poner la implementación en lo __new__que parece bastante hacky).

No podría haber __init__hecho nada si ya se ha inicializado, o podría escribir una nueva metaclase con una nueva __call__que solo llame __init__a nuevas instancias y, de lo contrario, solo regrese __new__(...).

Devin Jeanpierre
fuente
1

La razón simple es que lo nuevo se usa para crear una instancia, mientras que init se usa para inicializar la instancia. Antes de inicializar, la instancia debe crearse primero. Es por eso que la nueva debe ser llamado antes de init .

ZQ Hu
fuente
1

Ahora tengo el mismo problema, y ​​por algunas razones decidí evitar decoradores, fábricas y metaclases. Lo hice así:

Archivo principal

def _alt(func):
    import functools
    @functools.wraps(func)
    def init(self, *p, **k):
        if hasattr(self, "parent_initialized"):
            return
        else:
            self.parent_initialized = True
            func(self, *p, **k)

    return init


class Parent:
    # Empty dictionary, shouldn't ever be filled with anything else
    parent_cache = {}

    def __new__(cls, n, *args, **kwargs):

        # Checks if object with this ID (n) has been created
        if n in cls.parent_cache:

            # It was, return it
            return cls.parent_cache[n]

        else:

            # Check if it was modified by this function
            if not hasattr(cls, "parent_modified"):
                # Add the attribute
                cls.parent_modified = True
                cls.parent_cache = {}

                # Apply it
                cls.__init__ = _alt(cls.__init__)

            # Get the instance
            obj = super().__new__(cls)

            # Push it to cache
            cls.parent_cache[n] = obj

            # Return it
            return obj

Clases de ejemplo

class A(Parent):

    def __init__(self, n):
        print("A.__init__", n)


class B(Parent):

    def __init__(self, n):
        print("B.__init__", n)

En uso

>>> A(1)
A.__init__ 1  # First A(1) initialized 
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1)      # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2  # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
  • Advertencia: no debe inicializar Parent, ya que colisionará con otras clases, a menos que haya definido un caché separado en cada uno de los hijos, eso no es lo que queremos.
  • Advertencia: Parece que una clase con Parent como abuelo se comporta de manera extraña. [Inconfirmado]

Pruébalo en línea!

Trébol rojo
fuente