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__
tomaself
como 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
MyClass
esté vinculado a una función que devuelve instancias de la clase original. Pero ahora no hay forma de referirse a la clase original, y alMyClass
ser una función se rompeisinstance
/issubclass
verifica, el acceso a los atributos / métodos deMyClass.something
la clase directamente como , nombrar la clase ensuper
llamadas, 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 unaSingleton
metaclase 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
init
de__new__
sonidos realmente HACKY. Para eso están las metaclases.salidas:
Nota: como una
M._dict
propiedad de efectos secundarios se vuelve accesible automáticamenteA
,A._dict
así 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 sinsuper
argumentos 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
type
y 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_call
que luego llamatp_new
y luegotp_init
.La parte interesante aquí es que
tp_new
llamará alObject
nuevo método 's (clase base)object_new
que 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_init
se llama y no hace nada :)Luego
type_call
solo 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 lathis
variable. 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*args
y '** 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