Esta pregunta no es para la discusión de si el patrón de diseño único o no es deseable, es un antipatrón o para cualquier guerra religiosa, sino para discutir cómo este patrón se implementa mejor en Python de la manera más pitónica. En este caso, defino 'más pitónico' para que signifique que sigue el 'principio del menor asombro' .
Tengo varias clases que se convertirían en singletons (mi caso de uso es para un registrador, pero esto no es importante). No deseo saturar varias clases con gumph añadido cuando simplemente puedo heredar o decorar.
Mejores métodos:
Método 1: un decorador
def singleton(class_):
instances = {}
def getinstance(*args, **kwargs):
if class_ not in instances:
instances[class_] = class_(*args, **kwargs)
return instances[class_]
return getinstance
@singleton
class MyClass(BaseClass):
pass
Pros
- Los decoradores son aditivos de una manera que a menudo es más intuitiva que la herencia múltiple.
Contras
- Si bien los objetos creados con MyClass () serían verdaderos objetos singleton, MyClass en sí es una función, no una clase, por lo que no puede llamar a los métodos de clase desde ella. También para
m = MyClass(); n = MyClass(); o = type(n)();
entoncesm == n && m != o && n != o
Método 2: una clase base
class Singleton(object):
_instance = None
def __new__(class_, *args, **kwargs):
if not isinstance(class_._instance, class_):
class_._instance = object.__new__(class_, *args, **kwargs)
return class_._instance
class MyClass(Singleton, BaseClass):
pass
Pros
- Es una verdadera clase
Contras
- Herencia múltiple - ¡eugh!
__new__
podría sobrescribirse durante la herencia de una segunda clase base? Uno tiene que pensar más de lo necesario.
Método 3: una metaclase
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]
#Python2
class MyClass(BaseClass):
__metaclass__ = Singleton
#Python3
class MyClass(BaseClass, metaclass=Singleton):
pass
Pros
- Es una verdadera clase
- Auto-mágicamente cubre la herencia
- Usos
__metaclass__
para su propósito apropiado (y me hizo consciente de ello)
Contras
- ¿Hay alguna?
Método 4: decorador que devuelve una clase con el mismo nombre
def singleton(class_):
class class_w(class_):
_instance = None
def __new__(class_, *args, **kwargs):
if class_w._instance is None:
class_w._instance = super(class_w,
class_).__new__(class_,
*args,
**kwargs)
class_w._instance._sealed = False
return class_w._instance
def __init__(self, *args, **kwargs):
if self._sealed:
return
super(class_w, self).__init__(*args, **kwargs)
self._sealed = True
class_w.__name__ = class_.__name__
return class_w
@singleton
class MyClass(BaseClass):
pass
Pros
- Es una verdadera clase
- Auto-mágicamente cubre la herencia
Contras
- ¿No hay una sobrecarga para crear cada nueva clase? Aquí estamos creando dos clases para cada clase que deseamos hacer un singleton. Si bien esto está bien en mi caso, me preocupa que esto no pueda escalar. Por supuesto, hay una cuestión de debate sobre si sería demasiado fácil escalar este patrón ...
- ¿Cuál es el punto del
_sealed
atributo? - No se pueden llamar métodos del mismo nombre en clases base usando
super()
porque recurrirán . Esto significa que no puede personalizar__new__
y no puede subclasificar una clase que necesita llamar__init__
.
Método 5: un módulo
un archivo de módulo singleton.py
Pros
- Simple es mejor que complejo
Contras
foo.x
o si insistes enFoo.x
lugar deFoo().x
); use atributos de clase y métodos estáticos / de clase (Foo.x
).Respuestas:
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:
O en Python3
Si desea ejecutar
__init__
cada vez que se llama a la clase, agreguea 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 tipotype
.Logger
en el código anterior será de tipoclass 'your_module.Singleton'
, así como la (única) instancia deLogger
será de tipoclass 'your_module.Logger'
. Cuando se llama registrador conLogger()
, Python primera pregunta al metaclase deLogger
,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 haciendomyclass.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 llameSubClassOfSingleton()
, 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úblicoSingleton
de la clase base. Es más difícil de explicar que de hacer, como se ilustra a continuación: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 usarsuper()
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.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 consuper()
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 :
fuente
__new__
en una metaclase es cuando la clase es nueva, cuando está definida, no cuando la instancia sería nueva. Llamar a la clase (MyClass()
) es la operación que desea anular, no la definición de la clase. Si realmente quiere entender cómo funciona Python, lo mejor que puede hacer (aparte de seguir usándolo) es leer docs.python.org/reference/datamodel.html . Una buena referencia en metaclases es eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Un buen artículo sobre singletons es la serie del blog de google que vinculé en esta respuesta.Los módulos se importan solo una vez, todo lo demás está pensando demasiado. No use singletons e intente no usar globals.
fuente
s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
is
operador prueba la igualdad del puntero. Me sorprendería bastante --- hasta el punto de llamarlo un error --- sipickle.loads
devolviera una referencia a un objeto preexistente en lugar de una referencia a uno recién creado. Por lo tanto, probar sis is s1
no le dice nada sobre la idoneidad del uso de módulos como singletons.pickle.loads()
ya lo hace, por ejemplo, para instancias debool
yNoneType
.pickle.loads(pickle.dumps(False)) is False
rendimientosTrue
True
,False
yNone
, y no tiene nada que ver con el código detráspickle.loads
. Además, es seguro hacerlo solo para objetos de solo lectura. Sipickle.loads
devolviera una referencia a un objeto modificable ya existente , como un módulo, eso sería un error. (Y por lo tanto, estoy de acuerdo con mi implicación de que el ejemplo de código de dividebyzero no prueba nada).Usa un módulo. Se importa solo una vez. Defina algunas variables globales en él: serán los 'atributos' de singleton. Agregue algunas funciones: los 'métodos' del singleton.
fuente
Probablemente nunca necesite un singleton en Python. Simplemente defina todos sus datos y funciones en un módulo y tendrá un singleton de facto.
Si realmente tienes que tener una clase de singleton, entonces iría con:
Usar:
donde mysingleton.py es su nombre de archivo en el que se define My_Singleton. Esto funciona porque después de la primera vez que se importa un archivo, Python no vuelve a ejecutar el código.
fuente
Aquí hay una frase para usted:
Así es como lo usa:
Su objeto se instancia con entusiasmo. Esto puede o no ser lo que quieres.
fuente
type(wat)
owat.__class__
. Si realmente desea lograr esto, defina mejor la clase e instanciarla de inmediato, sin necesidad de meterse con el decorador.wat2 = type(wat)()
, pero esto es python, todos estamos consintiendo adultos y todo eso. No puede garantizar que solo habrá una instancia, pero puede garantizar que si las personas hacen una segunda, se verá fea y, si son personas decentes y respetuosas, como una señal de advertencia para ellos. ¿Qué me estoy perdiendo?Consulte la pregunta sobre desbordamiento de pila. ¿Hay una forma simple y elegante de definir singletons en Python? con varias soluciones
Recomiendo ver las charlas de Alex Martelli sobre patrones de diseño en python: parte 1 y parte 2 . En particular, en la parte 1 habla de objetos únicos / objetos de estado compartido.
fuente
Aquí está mi propia implementación de singletons. Todo lo que tienes que hacer es decorar la clase; para obtener el singleton, debes usar el
Instance
método. Aquí hay un ejemplo:Y aquí está el código:
fuente
SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())
debe imprimirTrue
en verdadero singleton; con su códigoFalse
El método 3 parece ser muy bueno, pero si desea que su programa se ejecute tanto en Python 2 como en Python 3 , no funciona. Incluso la protección de las variantes separadas con pruebas para la versión de Python falla, porque la versión de Python 3 da un error de sintaxis en Python 2.
Gracias a Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ . Si desea que el programa funcione tanto en Python 2 como en Python 3, debe hacer algo como:
Supongo que el 'objeto' en la asignación debe reemplazarse con la 'BaseClass', pero no lo he intentado (he intentado el código como se ilustra).
fuente
class MyClass(metaclass=Singleton)
Bueno, aparte de estar de acuerdo con la sugerencia general de Pythonic sobre tener un nivel de módulo global, ¿qué tal esto:
Salida es:
fuente
_sealed
atributo? Por lo que veo, ¿esto no hace nada? Algo me molesta sobre esto que dice que no debería funcionar bien ... Haré algunas pruebas comparativas más adelante esta semana.__init__
para que se llamen cada vez que se inicializa. Solo un simple 'Ser inicializado en class.method'. en cuanto a la sangría (usó pestañas y espacios), arreglé la mayor parte, pero parece haber perdido una si desea obtenerla (solo consulte el registro de edición)Qué tal esto:
Úselo como decorador en una clase que debería ser un singleton. Me gusta esto:
Esto es similar al
singleton = lambda c: c()
decorador en otra respuesta. Al igual que la otra solución, la única instancia tiene el nombre de la clase (MySingleton
). Sin embargo, con esta solución aún puede "crear" instancias (en realidad obtener la única instancia) de la clase, al hacerloMySingleton()
. También evita que crees instancias adicionales haciendotype(MySingleton)()
(eso también devuelve la misma instancia).fuente
type(MySingleton)()
,MySingleton.__init__()
recibe una llamada y el objeto se inicializa varias veces; puedes arreglarlo escribiendocls.__init__ = lambda self: pass
en tusingleton
. Además, la anulacióncls.__call__
parece inútil e incluso dañina:__call__
definida en este contexto se usa cuando llamasMySingleton(any, list, of, arguments)
, no cuando llamastype(MySingleton)(any, list, of, arguments)
.__init__()
vuelve a llamar al hacerlotype(MySingleton)()
. La solución que propuso (agregarcls.__init__ = lambda self: pass
) da un error de sintaxis, porque la última parte de la expresión lambda debe ser una expresión, no una declaración. Sin embargo, agregarcls.__init__ = lambda self: None
trabajos, así que agregué eso a mi respuesta.__call__
. mi intención era hacer ambas cosastype(MySingleton)()
yMySingleton()
devolver la instancia. Entonces está haciendo lo que quería. Puede pensar en MySingleton como el tipo de singleton o la instancia de singleton (o ambos).Tiraré el mío al ring. Es un simple decorador.
Beneficios que creo que tiene sobre algunas de las otras soluciones:
YourClass
. Esto incluye no necesitar usar una metaclase para su clase (tenga en cuenta que la metaclase anterior está en la fábrica, no la clase "real").YourClass
, parece una clase (porque lo es), y lo usan normalmente. No es necesario adaptar las llamadas a una función de fábrica.YourClass()
crea una instancia sigue siendo una verdadera instancia de loYourClass
que implementó, no un proxy de ningún tipo, por lo que no hay posibilidad de que se produzcan efectos secundarios.isinstance(instance, YourClass)
y operaciones similares todavía funcionan como se esperaba (aunque este bit requiere abc, por lo que excluye Python <2.6).Se me ocurre una desventaja: los métodos de clase y los métodos estáticos de la clase real no se pueden llamar de forma transparente a través de la clase de fábrica que lo oculta. He usado esto raramente lo suficiente como para nunca encontrarme con esa necesidad, pero sería fácilmente rectificado usando una metaclase personalizada en la fábrica que implemente
__getattr__()
para delegar el acceso a todos los atributos a la clase real.Un patrón relacionado que realmente he encontrado más útil (no es que esté diciendo que este tipo de cosas se requieren con mucha frecuencia) es un patrón "Único" en el que instanciar la clase con los mismos argumentos resulta en recuperar la misma instancia. Es decir, un "singleton por argumentos". Lo anterior se adapta bien a esto y se vuelve aún más conciso:
Dicho todo esto, estoy de acuerdo con el consejo general de que si cree que necesita una de estas cosas, probablemente debería detenerse por un momento y preguntarse si realmente lo necesita. 99% del tiempo, YAGNI.
fuente
serial
comunicación de , y para crear una instancia que desea enviar el puerto serie como argumento, entonces con el enfoque tradicional no funcionaráfuente
Código basado en la respuesta de Tolli .
Explicación:
Crea una nueva clase, heredando de dado
cls
(no se modifica
cls
en caso de que alguien quiera, por ejemplosingleton(list)
)Crear instancia Antes de anular
__new__
es tan fácil.__new__
utilizando el método definido hace un momento.La función regresa
instance
solo cuando es lo que la persona que llama espera, de lo contrario aumentaTypeError
.La condición no se cumple cuando alguien intenta heredar de la clase condecorada.
instance
ya está inicializado, por lo que la función reemplaza__init__
a la función que no hace nada.Véalo trabajando en línea
fuente
Es ligeramente similar a la respuesta de fab, pero no exactamente igual.
El contrato singleton no requiere que podamos llamar al constructor varias veces. Como un singleton debe crearse una vez y solo una vez, ¿no debería verse que se creó solo una vez? La "suplantación" del constructor podría afectar la legibilidad.
Entonces mi sugerencia es solo esto:
Esto no excluye el uso del constructor o el campo
instance
por el código de usuario:... si sabe con certeza que
Elvis
aún no se ha creado, y que síKing
.Pero alienta a los usuarios a usar el
the
método universalmente:Para completar esto, también puede anular
__delattr__()
para generar una Excepción si se intenta eliminarinstance
, y anular__del__()
para que genere una Excepción (a menos que sepamos que el programa está finalizando ...)Futuras mejoras
Mi agradecimiento a aquellos que han ayudado con comentarios y ediciones, de los cuales más son bienvenidos. Si bien uso Jython, esto debería funcionar de manera más general y ser seguro para subprocesos.
Puntos de nota:
__new__
__new__
debe decorar con @classmethod o__new__
será un método de instancia independientethe
una propiedad de nivel de clase, posiblemente renombrándola ainstance
fuente
__new
__ en lugar de hacerlo__init__
, ya que actúa puramente sobre los atributos de clase y esto evita que haya una segunda instancia brevemente. La diferencia entre este y el método 2 es si intentar inicializar más de una vez devuelve la instancia única o genera una excepción. Creo que estoy contento de que o satisfaga el patrón singleton, uno es más fácil de usar, mientras que el otro es más explícito que es un singleton.__init__
previene la subclasificación, pero al mismo tiempo esto hace las cosas más fáciles, no se requiere__init__
para que con suerte esto sea subclassable ...the
probablemente podría beneficiarse de ser un método de clase por razones similaresOne liner (no estoy orgulloso, pero hace el trabajo):
fuente
Si no necesita una inicialización diferida de la instancia de Singleton, lo siguiente debería ser fácil y seguro para subprocesos:
De esta manera,
A
se inicia un singleton en la importación del módulo.fuente
Tal vez no entiendo el patrón singleton, pero mi solución es simple y pragmática (¿pitónica?). Este código cumple dos objetivos.
Foo
accesible en todas partes (global).Foo
puede existir una instancia de .Este es el código.
Salida
fuente
Después de luchar con esto durante algún tiempo, finalmente se me ocurrió lo siguiente, de modo que el objeto de configuración solo se cargaría una vez, cuando se lo llama desde módulos separados. La metaclase permite que una instancia de clase global se almacene en el dicticio integrado, que en la actualidad parece ser la mejor manera de almacenar un programa global adecuado.
fuente
No recuerdo dónde encontré esta solución, pero creo que es la más 'elegante' desde mi punto de vista no experto en Python:
¿Por qué me gusta esto? Sin decoradores, sin metaclases, sin herencia múltiple ... y si decides que ya no quieres que sea Singleton, simplemente elimina el
__new__
método. Como soy nuevo en Python (y OOP en general) espero que alguien me aclare por qué este es un enfoque terrible.fuente
__new__
. No te repitas a ti mismo .*args
y**kwargs
, a continuación, no hace nada con ellos? Pasarlos endict.__new__
esta manera:dict.__new__(cls, *args, **kwargs)
.__init__
método cada vez que se llame a la clase. Si su__init__
método realmente hizo algo, notará el problema. Siempre que lo hagaSomeSingleton()
, el__init__
método restablecerá el estado de su singleton .Esta es mi forma preferida de implementar singletons:
fuente
Es probable que esta respuesta no sea lo que estás buscando. Quería un singleton en el sentido de que solo ese objeto tuviera su identidad, para compararlo. En mi caso, se estaba utilizando como valor centinela . Para lo cual la respuesta es muy simple, haga cualquier objeto
mything = object()
y, por naturaleza de Python, solo esa cosa tendrá su identidad.fuente
eval
oimportlib.reload
.Esta solución causa cierta contaminación del espacio de nombres a nivel de módulo (tres definiciones en lugar de solo una), pero me resulta fácil de seguir.
Me gustaría poder escribir algo como esto (inicialización diferida), pero desafortunadamente las clases no están disponibles en el cuerpo de sus propias definiciones.
Como eso no es posible, podemos dividir la inicialización y la instancia estática en
Inicialización ansiosa:
Inicialización perezosa:
Inicialización ansiosa:
fuente