python - software - strategy pattern django
¿Por qué siempre se llama a__init__() después de__new__()? (17)
Solo estoy tratando de agilizar una de mis clases y he introducido alguna funcionalidad 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 esperaba esto. ¿Alguien puede decirme por qué sucede esto y cómo puedo implementar esta funcionalidad de otra manera? (Aparte de poner la implementación en el __new__
que se siente bastante intrépido).
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é?
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 sí lo tiene ( __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 el
__new__
que se siente bastante intrépido).
Podría hacer que __init__
no haga nada si ya se ha inicializado, o podría escribir una nueva metaclase con una nueva __call__
que solo llame a __init__
en nuevas instancias, y de lo contrario solo devolverá __new__(...)
.
Utilice __new__ cuando necesite controlar la creación de una nueva instancia. Utilice __init__ cuando necesite controlar la inicialización de una nueva instancia.
__new__ es el primer paso de la creación de la instancia. Se llama primero y es responsable de devolver una nueva instancia de su clase. En contraste, __init__ no devuelve nada; solo es responsable de inicializar la instancia una vez creada.
En general, no debería tener que reemplazar __new__ a menos que esté subclasificando un tipo inmutable como str, int, unicode o tuple.
De: http://mail.python.org/pipermail/tutor/2008-April/061426.html
Debes considerar que lo que intentas hacer generalmente se hace con una Factory y esa es la mejor manera de hacerlo. Usar __new__ no es una buena solución limpia, por lo tanto, considere el uso de una fábrica. Aquí tienes un buen ejemplo de fábrica .
¡Cavando 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 here . 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 nuevo método tp_new
del Object
(clase base) que 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 se __init__
método __init__
. Si __init__
no se implementa en su clase, entonces se llama a object_init
y no hace nada :)
Luego, type_call
simplemente devuelve el objeto que se enlaza a su variable.
Ahora tengo el mismo problema, y por algunas razones decidí evitar a los 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 haya definido un caché separado en cada uno de los hijos, eso no es lo que queremos.
- Advertencia: Parece que una clase con padres como abuelos se comporta de forma extraña. [Inconfirmado]
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__
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__
que se ejecutará __init__
.
Cuando __new__
devuelve la 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 el 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 arriba 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á here .
Otro enfoque, que de hecho involucra a __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"
Preste atención, ya que con este enfoque necesita decorar TODOS sus métodos con @classmethod
, porque nunca usará una instancia real de MyClass
.
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 de "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 cualquier condición inicial deseada. En Python, esto corresponde al método de la clase __init__
.
El __new__
de Python no 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 que esta parte implica necesariamente una asignación; todo lo que necesitamos es que __new__
una instancia adecuada de algún lugar.
Pero aún 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 necesite __new__
, o para que no necesite __new__
, o para que __init__
se __init__
manera diferente en un objeto ya inicializado. Pero si realmente quieres, Python realmente te permite redefinir "el trabajo", por lo que SomeClass(arg1, arg2)
no necesariamente llama __new__
seguido de __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 de clase '' __call__
controla lo que sucede cuando llama a instancias de la clase. Entonces, un método de metaclase '' __call__
controla lo que sucede cuando llamas 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 de singleton. De hecho, con menos de 10 líneas de código, puede implementar una metaclase de Singleton
que luego no requiere que use futz con __new__
en absoluto , y puede convertir cualquier clase normal en un singleton simplemente agregando __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.
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 se debe llamar nuevo antes de init .
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
Utilicé esta página como recurso http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
Para citar la documentation :
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, entonces el método __init __ () de la nueva instancia no será invocado.
__new __ () está destinado principalmente a permitir que las subclases de tipos inmutables (como int, str o tuple) personalicen la creación de instancias.
Se llama al __new__
después de __new__
modo que cuando lo __new__
en una subclase, su código agregado seguirá siendo llamado.
Si está intentando subclasificar una clase que ya tiene un __new__
, alguien que no lo __new__
podría comenzar por adaptar el __init__
y reenviar la llamada a la subclase __init__
. Esta convención de llamar a __init__
después de __new__
ayuda a que funcione como se espera.
El __init__
aún debe permitir los parámetros necesarios para la superclase __new__
, pero si no lo hace, generalmente se creará un 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 está bien.
En general, es una mala forma tener a __new__
y __init__
en la misma clase en el mismo nivel de herencia, debido al comportamiento que describió el póster original.
Una actualización de @AntonyHatchkins responde, es probable que desee un diccionario de instancias por separado 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()
__init__
y actualicé el código original con un método __init__
y cambié la sintaxis a la notación de Python 3 (llamada no-arg a super
y metaclass 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 (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__
.
Uno debe ver a __init__
como un simple constructor en lenguajes OO tradicionales. Por ejemplo, si está familiarizado con Java o C ++, al constructor se le pasa un puntero a su propia instancia de forma implícita. En el caso de Java, es la variable this
. Si uno inspeccionara el código de byte generado para Java, notaría dos llamadas. La primera llamada es a un "nuevo" método, 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, __new__
es una facilidad adicional que es accesible para el usuario. Java no proporciona esa flexibilidad, debido a su naturaleza tipificada. Si un lenguaje proporcionó 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 para los tipos inmutables en el caso de Python.
__new__ debe devolver una nueva instancia en blanco de una clase. __init__ se llama para inicializar esa instancia. No estás llamando a __init__ en el caso "NUEVO" de __nuevo__, por lo que te están llamando. El código que está llamando a __new__
no hace un seguimiento de si __init__ se ha llamado en una instancia en particular o no, ni 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 la primera cosa en __init__ y no continúe más si ha sido.
Refiriéndose a este documento :
Al subclasificar tipos incorporados inmutables como números y cadenas, y ocasionalmente en otras situaciones, el nuevo método estático es útil. Nuevo es el primer paso en la construcción de la instancia, 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 esa clase.
Compare esto con init : se llama a init con una instancia como su 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 una nueva (aunque en algunos casos puede salirse con la suya llamando a una nueva clase base).
Con respecto 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 de 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:
...
__new__
es un método de clase estático, mientras que __init__
es un método de instancia. __new__
tiene que crear la instancia primero, por lo que __init__
puede inicializarla. Tenga en cuenta que __init__
toma self
como parámetro. Hasta que creas instancia no hay self
.
Ahora, me imagino que estás intentando implementar un patrón de singleton en Python. Hay algunas maneras de hacer eso.
Además, a partir de Python 2.6, puede utilizar decorators clase.
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
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 M._dict
propiedad M._dict
se vuelve automáticamente accesible desde A
como A._dict
así que tenga cuidado de no sobrescribirla de forma incidental.