strategy software patterns pattern patrones faif diseño book python design-patterns class-design

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]

Pruébalo en línea!


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.