Use una metaclase
Recomendaría el Método # 2 , pero es mejor usar una metaclase que una clase base. Aquí hay una implementación de muestra:
class Singleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Logger(object):
__metaclass__ = Singleton
O en Python3
class Logger(metaclass=Singleton):
pass
Si desea ejecutar __init__
cada vez que se llama a la clase, agregue
else:
cls._instances[cls].__init__(*args, **kwargs)
a la if
declaración enSingleton.__call__
.
Algunas palabras sobre metaclases. Una metaclase es la clase de una clase ; es decir, una clase es una instancia de su metaclase . Encuentra la metaclase de un objeto en Python con type(obj)
. Las clases normales de nuevo estilo son de tipo type
. Logger
en el código anterior será de tipo class 'your_module.Singleton'
, así como la (única) instancia de Logger
será de tipo class 'your_module.Logger'
. Cuando se llama registrador con Logger()
, Python primera pregunta al metaclase de Logger
, Singleton
, qué hacer, lo que permite la creación de instancias que se ha vaciado anteriormente. Este proceso es el mismo que Python pregunta a una clase qué hacer llamando __getattr__
al hacer referencia a uno de sus atributos haciendo myclass.attribute
.
Una metaclase esencialmente decide qué significa la definición de una clase y cómo implementar esa definición. Consulte, por ejemplo , http://code.activestate.com/recipes/498149/ , que esencialmente recrea los C-style struct
en Python usando metaclases. El hilo ¿Cuáles son algunos casos de uso (concretos) para metaclases?también proporciona algunos ejemplos, generalmente parecen estar relacionados con la programación declarativa, especialmente como se usa en ORM.
En esta situación, si usa su Método # 2 , y una subclase define un __new__
método, se ejecutará cada vez que llame SubClassOfSingleton()
, porque es responsable de llamar al método que devuelve la instancia almacenada. Con una metaclase, solo se llamará una vez , cuando se cree la única instancia. Desea personalizar lo que significa llamar a la clase , que se decide por su tipo.
En general, tiene sentido usar una metaclase para implementar un singleton. Un singleton es especial porque se crea solo una vez , y una metaclase es la forma en que personaliza la creación de una clase . El uso de una metaclase le brinda más control en caso de que necesite personalizar las definiciones de clase singleton de otras maneras.
Sus singletons no necesitarán herencia múltiple (porque la metaclase no es una clase base), pero para las subclases de la clase creada que usan herencia múltiple, debe asegurarse de que la clase singleton sea la primera / izquierda con una metaclase que redefina __call__
Es muy poco probable que esto sea un problema. La instancia dict no está en el espacio de nombres de la instancia, por lo que no la sobrescribirá accidentalmente.
También escuchará que el patrón singleton viola el "Principio de responsabilidad única": cada clase debe hacer solo una cosa . De esa manera, no tiene que preocuparse por estropear una cosa que hace el código si necesita cambiar otra, porque están separadas y encapsuladas. La implementación de metaclase pasa esta prueba . La metaclase es responsable de aplicar el patrón y la clase y las subclases creadas no necesitan ser conscientes de que son singletons . El método # 1 falla esta prueba, como notó con "MyClass en sí es una función, no una clase, por lo que no puede llamar a los métodos de clase desde ella".
Versión compatible con Python 2 y 3
Escribir algo que funcione tanto en Python2 como en 3 requiere usar un esquema un poco más complicado. Desde metaclases suelen ser subclases de tipo type
, es posible usar uno para crear dinámicamente una clase base intermediario en tiempo de ejecución con él como su metaclase y luego usar que como los baseclass del público Singleton
de la clase base. Es más difícil de explicar que de hacer, como se ilustra a continuación:
# works in Python 2 & 3
class _Singleton(type):
""" A metaclass that creates a Singleton base class when called. """
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
return cls._instances[cls]
class Singleton(_Singleton('SingletonMeta', (object,), {})): pass
class Logger(Singleton):
pass
Un aspecto irónico de este enfoque es que utiliza subclases para implementar una metaclase. Una posible ventaja es que, a diferencia de una metaclase pura, isinstance(inst, Singleton)
volverá True
.
Correcciones
En otro tema, probablemente ya lo haya notado, pero la implementación de la clase base en su publicación original es incorrecta. _instances
necesita ser referenciado en la clase , debe usar super()
o está recurriendo , y en __new__
realidad es un método estático al que debe pasar la clase , no un método de clase, ya que la clase real aún no se ha creado cuando se llama. Todas estas cosas serán ciertas para una implementación de metaclase también.
class Singleton(object):
_instances = {}
def __new__(class_, *args, **kwargs):
if class_ not in class_._instances:
class_._instances[class_] = super(Singleton, class_).__new__(class_, *args, **kwargs)
return class_._instances[class_]
class MyClass(Singleton):
pass
c = MyClass()
Decorador devolviendo una clase
Originalmente estaba escribiendo un comentario pero era demasiado largo, así que agregaré esto aquí. El Método # 4 es mejor que la otra versión del decorador, pero es más código del necesario para un singleton, y no está tan claro lo que hace.
Los principales problemas se derivan de que la clase es su propia clase base. Primero, ¿no es extraño que una clase sea una subclase de una clase casi idéntica con el mismo nombre que existe solo en su __class__
atributo? Esto también significa que no se puede definir cualquier método que llaman al método del mismo nombre en la clase base con super()
porque van a recursiva. Esto significa que su clase no puede personalizarse __new__
, y no puede derivarse de ninguna clase que necesite __init__
invocarlos.
Cuándo usar el patrón singleton
Su caso de uso es uno de los mejores ejemplos de querer usar un singleton. Usted dice en uno de los comentarios "Para mí, el registro siempre me ha parecido un candidato natural para Singletons". Tienes toda la razón .
Cuando las personas dicen que los singletons son malos, la razón más común es que son estados compartidos implícitos . Mientras que con las variables globales y las importaciones de módulos de nivel superior son un estado compartido explícito , otros objetos que se pasan generalmente se instancian. Este es un buen punto, con dos excepciones .
El primero, y uno que se menciona en varios lugares, es cuando los singletons son constantes . El uso de constantes globales, especialmente enumeraciones, es ampliamente aceptado y considerado sensato porque no importa qué, ninguno de los usuarios puede confundirlos con ningún otro usuario . Esto es igualmente cierto para un singleton constante.
La segunda excepción, que se menciona menos, es lo contrario: cuando el singleton es solo un sumidero de datos , no una fuente de datos (directa o indirectamente). Esta es la razón por la cual los registradores se sienten como un uso "natural" para los singletons. Como los diversos usuarios no están cambiando los registradores de la forma en que otros usuarios se preocuparán, en realidad no hay un estado compartido . Esto niega el argumento principal contra el patrón singleton y los convierte en una opción razonable debido a su facilidad de uso para la tarea.
Aquí hay una cita de http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
Ahora, hay un tipo de Singleton que está bien. Ese es un singleton donde todos los objetos accesibles son inmutables. Si todos los objetos son inmutables, Singleton no tiene un estado global, ya que todo es constante. Pero es tan fácil convertir este tipo de singleton en mutable, es una pendiente muy resbaladiza. Por lo tanto, también estoy en contra de estos Singletons, no porque sean malos, sino porque es muy fácil que se vuelvan malos. (Como nota al margen, la enumeración de Java es solo este tipo de singletons. Siempre que no ponga el estado en su enumeración, está bien, así que no lo haga).
El otro tipo de Singletons, que son semi-aceptables, son aquellos que no afectan la ejecución de su código, no tienen "efectos secundarios". El registro es un ejemplo perfecto. Está cargado con Singletons y estado global. Es aceptable (ya que no le hará daño) porque su aplicación no se comporta de manera diferente si un registrador determinado está habilitado o no. La información aquí fluye de una manera: desde su aplicación al registrador. Incluso si los registradores son de estado global, ya que no fluye información de los registradores a su aplicación, los registradores son aceptables. Todavía debe inyectar su registrador si desea que su prueba afirme que algo se está registrando, pero en general los registradores no son dañinos a pesar de estar llenos de estado.
foo.x
o si insistes enFoo.x
lugar deFoo().x
); use atributos de clase y métodos estáticos / de clase (Foo.x
).