¿Por qué se llama siempre a init () después de new ()?


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 en cuanto a por qué __init__ siempre se llama después de __new__. No me esperaba esto. ¿Puede alguien decirme por qué está sucediendo esto y cómo puedo implementar esta funcionalidad de lo contrario? (Aparte de poner la implementación en el __new__ que se siente 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é?

Author: martineau, 2009-03-23

17 answers

Use __nuevo _ _ cuando necesite controlar la creación de una nueva instancia. Utilizar __init__ cuando necesite controlar la inicialización de una nueva instancia.

__new _ _ es el primer paso para crear una instancia. Se llama primero, y es responsable de devolver un nuevo ejemplo de tu clase. En contraste, _ _ init _ _ no devuelve nada; solo es responsable de inicializar instancia después de que se ha creado.

En general, no debería necesitarlo. anular __nuevo _ _ a menos que esté subclasificación de un tipo inmutable como str, int, unicode o tupla.

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

Debes considerar que lo que estás tratando de hacer generalmente se hace con una Fábrica y esa es la mejor manera de hacerlo. Usar __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 .

 479
Author: mpeterson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-23 17:31:09

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

Ahora, deduzco, que estás tratando de implementar patrón de singleton en Python. Hay algunas maneras de hacerlo.

También, a partir de Python 2.6, puede usar class decoradores.

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

@singleton
class MyClass:
  ...
 149
Author: vartec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-22 15:39:08

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 solo un bloque de código que opera en la nueva instancia (utilizando los argumentos proporcionados a la expresión del constructor) para configurar deseable. En Python, esto corresponde al método class' __init__.

El __new__ de Python es nada más y nada menos que una personalización similar por clase 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. Así que en Python, realmente no deberíamos pensar en esta parte como necesariamente implicando asignación; todo lo que necesitamos es que __new__ se le ocurra una instancia adecuada de en alguna parte.

Pero sigue siendo solo la mitad del trabajo, y no hay forma de que el sistema Python sepa que a veces quieres ejecutar la otra mitad del trabajo (__init__) después y a veces no. Si quieres ese comportamiento, tienes que decirlo explícitamente.

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

Una metaclase es solo la clase de una clase. Y un método class' __call__ controla lo que sucede cuando llamas a instancias de la clase. Así que una metaclase ' __call__ method 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 que puede implementar con mayor elegancia 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 metaclase Singleton que luego ni siquiera requiere que futz con __new__ en absoluto, y puede convertir cualquier de otra manera-clase normal en un singleton 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, esto es probablemente una magia más profunda de lo que realmente se justifica para esta situación!

 126
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-12-29 07:39:47

Para citar la documentación :

Las implementaciones típicas crean una nueva instancia de la clase invocando el método __new__() de la superclass usando " super (currentclass, cls).__nuevo__(cls[, ...]) "con argumentos apropiados y luego modificar la instancia recién creada según sea necesario antes de devolverla.

...

Si _ _ new _ _ () no devuelve una instancia de cls, entonces el nuevo el método __init__() de la instancia no será invocado.

_ _ new _ _ () está destinado principalmente a permitir subclases de tipos (como int, str o tupla) para personalizar la creación de instancias.

 20
Author: tgray,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-06-28 13:07:59

Me doy cuenta de que esta pregunta es bastante antigua, pero tenía un problema similar. Los siguientes hicieron lo que yo 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

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

 10
Author: Tobias,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-05-13 14:31:49

Creo que la respuesta simple a esta pregunta es que, si __new__ devuelve un valor que es del mismo tipo que la clase, la función __init__ se ejecuta, de lo contrario no lo hará. A._dict('key') que es la misma clase que cls, así que __init__ se ejecutará.

 8
Author: PMN,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-07-07 14:45:17

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

Aquí está el enfoque genérico para el patrón Singleton que extiende la respuesta de vartec anterior y lo 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 presta atención, que con este enfoque necesitas decorar TODOS tus métodos con @classmethod, porque nunca usarás ninguna instancia real de MyClass.

 8
Author: Zaar Hai,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-02-01 10:17:54
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 la propiedad automáticamente se vuelve accesible desde A como A._dict así que tenga cuidado de no sobrescribirla incidentalmente.

 5
Author: Antony Hatchkins,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-30 12:37:18

__new__ debe 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 está siendo llamado para usted. El código que está llamando __new__ no realiza un seguimiento de si __init__ ha sido llamado en una instancia en particular o no, ni debería, porque estás haciendo algo muy inusual aquí.

Podría agregar un atributo al objeto en la función__ init _ _ para indicar que ha sido inicialización. Compruebe la existencia de ese atributo como lo primero en __init__ y no continúe si lo ha sido.

 4
Author: Andrew Wilkinson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-23 17:26:35

Una actualización de la respuesta @AntonyHatchkins, probablemente desee un diccionario separado de instancias para cada clase del metatipo, lo que significa que debería 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 seguido adelante y actualizado el código original con un método __init__ y cambiado la sintaxis a la notación Python 3 (llamada no-arg a super y metaclase en los argumentos de la clase en su lugar de como atributo).

De cualquier manera, el punto importante aquí es que su inicializador de clases (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__.

 4
Author: Mad Physicist,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-18 19:39:06

En referencia a este documento :

Cuando subclase tipos integrados inmutables como números y cadenas, y de vez en cuando en otras situaciones, el método estático nuevo viene muy útil. new es el primer paso en la construcción de instancias, invocado antes de init .

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

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

Hay situaciones donde se crea una nueva instancia sin llamar a init (por ejemplo cuando la instancia se carga desde un pickle). No hay manera de crear una nueva instancia sin llamar a new (aunque en algunos casos puede salirse con la suya llamando a una clase base nuevo).

Con respecto a lo que desea lograr, también hay en la misma información doc sobre 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:
...
 3
Author: octoback,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-09-30 06:13:28

Excavación poco más 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 aquí. El primer método llamado es el type_call que luego llama tp_new y luego tp_init.

La parte interesante aquí es que tp_new llamará al 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 se crea en memoria y luego se llama al método __init__. Si __init__ no se implementa en su clase, entonces el object_init se llama y no hace nada:)

Luego type_call simplemente devuelve el objeto que se une a su variable.

 3
Author: xpapazaf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-11-15 20:47:55

Uno debería mirar a __init__ como un constructor simple en lenguajes OO tradicionales. Por ejemplo, si está familiarizado con Java o C++, al constructor se le pasa un puntero a su propia instancia implícitamente. En el caso de Java, es la variable this. Si uno inspeccionara el código de bytes generado para Java, 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 habilita 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 facilidad añadida que es accesible para el usuario. Java no proporciona esa flexibilidad, debido a su naturaleza tipificada. Si un lenguaje proporciona 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.

 3
Author: Guru Devanla,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-25 09:32:19

El __init__ se llama después de __new__ de modo que cuando lo sobreescribes en una subclase, tu código agregado todavía será llamado.

Si está tratando de subclasificar una clase que ya tiene un __new__, alguien que no sepa esto podría comenzar adaptando el __init__ y reenviando la llamada a la subclase __init__. Esta convención de llamar __init__ después de __new__ ayuda a que funcione como se espera.

El __init__ todavía necesita permitir cualquier parámetro que la superclase __new__ necesite, pero no lo hace por lo general, creará un error de tiempo de ejecución claro. Y el __new__ probablemente debería permitir explícitamente *args y '**kw', para dejar claro que la extensión está bien.

Generalmente es una mala forma tener __new__ y __init__ en la misma clase en el mismo nivel de herencia, debido al comportamiento descrito por el póster original.

 1
Author: Jay Obermark,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-06-18 16:30:45

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

No hay otra razón que no sea que simplemente se hace de esa manera. __new__ no tiene la responsabilidad de inicializar la clase, algún otro método lo hace (__call__, posiblemente't no lo sé con seguridad).

No me esperaba esto. ¿Puede alguien decirme por qué está sucediendo esto y cómo implemento esta funcionalidad de lo contrario? (aparte de poner la aplicación en el __new__ que se siente bastante hacky).

Puede hacer que __init__ no haga nada si ya se ha inicializado, o puede escribir una nueva metaclase con un nuevo __call__ que solo llame a __init__ en instancias nuevas, y de lo contrario solo devuelva __new__(...).

 0
Author: Devin Jeanpierre,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-03-23 17:23:37

La sencilla razón es que el new 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 new debe ser llamado antes de init .

 0
Author: ZQ Hu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-23 13:48:43

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, chocará con otras clases - a menos que defina caché separada en cada uno de los hijos, eso no es lo que queremos.
  • Advertencia: Parece una clase con Padre como el abuelo se comporta raro. [No verificado]

Pruébelo en línea!

 0
Author: Soaku,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-29 20:40:37