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é?

Respuestas:
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 .fuente
__new__dentro de una clase Factory, que se ha vuelto bastante limpia, así que gracias por su aporte.__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.__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__tomaselfcomo parámetro. Hasta que cree una instancia no hayself.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 .
fuente
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 alMyClassser una función se rompeisinstance/issubclassverifica, el acceso a los atributos / métodos deMyClass.somethingla clase directamente como , nombrar la clase ensuperllamadas, etc., etc. Si eso es un problema o no, depende de clase a la que lo estás aplicando (y al resto del programa).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 esoSomeClass(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 unaSingletonmetaclase 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!¡Sin embargo, esta es probablemente una magia más profunda de lo que realmente se justifica para esta situación!
fuente
Para citar la documentación :
fuente
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 .__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 .Me doy cuenta de que esta pregunta es bastante antigua, pero tuve un problema similar. Lo siguiente hizo lo que quería:
Usé esta página como un recurso http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
fuente
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 devuelveA._dict('key')que es la misma clase quecls, por__init__lo que se ejecutará.fuente
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:
La historia completa está aquí .
Otro enfoque, que de hecho implica
__new__es usar métodos de clase:Por favor, preste atención, con este enfoque necesita decorar TODOS sus métodos
@classmethod, porque nunca usará ninguna instancia real deMyClass.fuente
En referencia a este documento :
En cuanto a lo que desea lograr, también hay información en el mismo documento sobre el patrón Singleton
También puede usar esta implementación desde PEP 318, usando un decorador
fuente
initde__new__sonidos realmente HACKY. Para eso están las metaclases.salidas:
Nota: como una
M._dictpropiedad de efectos secundarios se vuelve accesible automáticamenteA,A._dictasí que tenga cuidado de no sobrescribirla de forma incidental.fuente
__init__método que establececls._dict = {}. Probablemente no desee un diccionario compartido por todas las clases de este metatipo (pero +1 para la idea).__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.
fuente
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.Continué y actualicé el código original con un
__init__método y cambié la sintaxis a la notación de Python 3 (llamada sinsuperargumentos 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 .fuente
¡Profundizando un poco más en eso!
El tipo de una clase genérica en CPython es
typey su clase base esObject(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 eltype_callque luego llamatp_newy luegotp_init.La parte interesante aquí es que
tp_newllamará alObjectnuevo método 's (clase base)object_newque hace untp_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.fuente
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 lathisvariable. 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.fuente
Creo que la analogía de C ++ sería útil aquí:
__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 .__init__Inicializa las variables internas del objeto a valores específicos (podría ser por defecto).fuente
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.fuente
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 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__(...).fuente
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 .
fuente
Ahora tengo el mismo problema, y por algunas razones decidí evitar decoradores, fábricas y metaclases. Lo hice así:
Archivo principal
Clases de ejemplo
En uso
Pruébalo en línea!
fuente