python - ¿Hay una manera simple y elegante de definir singletons?
python singleton object (22)
Aquí está mi propia implementación de singletons. Todo lo que tienes que hacer es decorar la clase; para obtener el singleton, entonces tienes que usar el método de Instance
. Aquí hay un ejemplo:
@Singleton
class Foo:
def __init__(self):
print ''Foo created''
f = Foo() # Error, this isn''t how you get the instance of a singleton
f = Foo.instance() # Good. Being explicit is in line with the Python Zen
g = Foo.instance() # Returns already created instance
print f is g # True
Y aquí está el código:
class Singleton:
"""
A non-thread-safe helper class to ease implementing singletons.
This should be used as a decorator -- not a metaclass -- to the
class that should be a singleton.
The decorated class can define one `__init__` function that
takes only the `self` argument. Also, the decorated class cannot be
inherited from. Other than that, there are no restrictions that apply
to the decorated class.
To get the singleton instance, use the `instance` method. Trying
to use `__call__` will result in a `TypeError` being raised.
"""
def __init__(self, decorated):
self._decorated = decorated
def instance(self):
"""
Returns the singleton instance. Upon its first call, it creates a
new instance of the decorated class and calls its `__init__` method.
On all subsequent calls, the already created instance is returned.
"""
try:
return self._instance
except AttributeError:
self._instance = self._decorated()
return self._instance
def __call__(self):
raise TypeError(''Singletons must be accessed through `instance()`.'')
def __instancecheck__(self, inst):
return isinstance(inst, self._decorated)
Esta pregunta ya tiene una respuesta aquí:
- Creando un singleton en Python 19 respuestas
Parece que hay muchas maneras de definir singletons en Python. ¿Hay una opinión de consenso sobre el desbordamiento de pila?
Aquí hay un ejemplo de Python IAQ de Peter Norvig. ¿Cómo hago el patrón Singleton en Python? (Debería usar la función de búsqueda de su navegador para encontrar esta pregunta, no hay un enlace directo, lo siento)
También Bruce Eckel tiene otro ejemplo en su libro Thinking in Python (de nuevo, no hay un enlace directo al código)
Como dice la respuesta aceptada , la forma más idiomática es usar un módulo .
Con eso en mente, aquí hay una prueba de concepto:
def singleton(cls):
obj = cls()
# Always return the same object
cls.__new__ = staticmethod(lambda cls: obj)
# Disable __init__
try:
del cls.__init__
except AttributeError:
pass
return cls
Ver el modelo de datos de Python para más detalles en __new__
.
Ejemplo:
@singleton
class Duck(object):
pass
if Duck() is Duck():
print "It works!"
else:
print "It doesn''t work!"
Notas:
Tienes que usar clases de nuevo estilo (derivar de
object
) para esto.El singleton se inicializa cuando se define, en lugar de la primera vez que se utiliza.
Esto es solo un ejemplo de juguete. Nunca he usado esto en el código de producción, y no planeo hacerlo.
Crear un decorador de singleton (también conocido como una anotación) es una forma elegante si desea decorar (anotar) las clases en el futuro. Luego, solo debes poner @singleton antes de la definición de tu clase.
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
Creo que forzar a una clase o una instancia a ser un singleton es una exageración. Personalmente, me gusta definir una clase normalizable, una referencia semiprivada y una simple función de fábrica.
class NothingSpecial:
pass
_the_one_and_only = None
def TheOneAndOnly():
global _the_one_and_only
if not _the_one_and_only:
_the_one_and_only = NothingSpecial()
return _the_one_and_only
O si no hay ningún problema con la creación de instancias cuando el módulo se importa por primera vez:
class NothingSpecial:
pass
THE_ONE_AND_ONLY = NothingSpecial()
De esa manera, puede escribir pruebas contra instancias nuevas sin efectos secundarios, y no hay necesidad de rociar el módulo con declaraciones globales, y si es necesario, puede derivar variantes en el futuro.
El enfoque del módulo funciona bien. Si necesito absolutamente un singleton, prefiero el enfoque de Metaclass.
class Singleton(type):
def __init__(cls, name, bases, dict):
super(Singleton, cls).__init__(name, bases, dict)
cls.instance = None
def __call__(cls,*args,**kw):
if cls.instance is None:
cls.instance = super(Singleton, cls).__call__(*args, **kw)
return cls.instance
class MyClass(object):
__metaclass__ = Singleton
En los casos en los que no desea la solución basada en metaclase anterior y no le gusta el enfoque basado en el decorador de función simple (por ejemplo, porque en ese caso los métodos estáticos en la clase singleton no funcionarán), este compromiso funciona:
class singleton(object):
"""Singleton decorator."""
def __init__(self, cls):
self.__dict__[''cls''] = cls
instances = {}
def __call__(self):
if self.cls not in self.instances:
self.instances[self.cls] = self.cls()
return self.instances[self.cls]
def __getattr__(self, attr):
return getattr(self.__dict__[''cls''], attr)
def __setattr__(self, attr, value):
return setattr(self.__dict__[''cls''], attr, value)
La única vez que escribí un singleton en Python usé una clase donde todas las funciones miembro tenían el decorador de método de clase.
class foo:
x = 1
@classmethod
def increment(cls, y = 1):
cls.x += y
La documentación de Python cubre esto:
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
Probablemente lo reescribiría para parecerme más a esto:
class Singleton(object):
"""Use to create a singleton"""
def __new__(cls, *args, **kwds):
"""
>>> s = Singleton()
>>> p = Singleton()
>>> id(s) == id(p)
True
"""
self = "__self__"
if not hasattr(cls, self):
instance = object.__new__(cls)
instance.init(*args, **kwds)
setattr(cls, self, instance)
return getattr(cls, self)
def init(self, *args, **kwds):
pass
Debe ser relativamente limpio para extender esto:
class Bus(Singleton):
def init(self, label=None, *args, **kwds):
self.label = label
self.channels = [Channel("system"), Channel("app")]
...
Mi solución simple que se basa en el valor predeterminado de los parámetros de la función.
def getSystemContext(contextObjList=[]):
if len( contextObjList ) == 0:
contextObjList.append( Context() )
pass
return contextObjList[0]
class Context(object):
# Anything you want here
No estoy muy seguro de esto, pero mi proyecto utiliza ''singletons convencionales'' (no singletons forzados), es decir, si tengo una clase llamada DataController
, lo defino en el mismo módulo:
_data_controller = None
def GetDataController():
global _data_controller
if _data_controller is None:
_data_controller = DataController()
return _data_controller
No es elegante, ya que es un total de seis líneas. Pero todos mis singletons usan este patrón, y es al menos muy explícito (que es pythonic).
OK, Singleton puede ser bueno o malo, lo sé. Esta es mi implementación, y simplemente extiendo un enfoque clásico para introducir un caché en el interior y generar muchas instancias de un tipo diferente o, muchas instancias del mismo tipo, pero con diferentes argumentos.
Lo llamé Singleton_group, porque agrupa instancias similares e impide que se pueda crear un objeto de la misma clase, con los mismos argumentos:
# Peppelinux''s cached singleton
class Singleton_group(object):
__instances_args_dict = {}
def __new__(cls, *args, **kwargs):
if not cls.__instances_args_dict.get((cls.__name__, args, str(kwargs))):
cls.__instances_args_dict[(cls.__name__, args, str(kwargs))] = super(Singleton_group, cls).__new__(cls, *args, **kwargs)
return cls.__instances_args_dict.get((cls.__name__, args, str(kwargs)))
# It''s a dummy real world use example:
class test(Singleton_group):
def __init__(self, salute):
self.salute = salute
a = test(''bye'')
b = test(''hi'')
c = test(''bye'')
d = test(''hi'')
e = test(''goodbye'')
f = test(''goodbye'')
id(a)
3070148780L
id(b)
3070148908L
id(c)
3070148780L
b == d
True
b._Singleton_group__instances_args_dict
{(''test'', (''bye'',), ''{}''): <__main__.test object at 0xb6fec0ac>,
(''test'', (''goodbye'',), ''{}''): <__main__.test object at 0xb6fec32c>,
(''test'', (''hi'',), ''{}''): <__main__.test object at 0xb6fec12c>}
Cada objeto lleva el caché de singleton ... Esto podría ser malo, pero funciona muy bien para algunos :)
Puede anular el método __new__
esta manera:
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(
cls, *args, **kwargs)
return cls._instance
if __name__ == ''__main__'':
s1 = Singleton()
s2 = Singleton()
if (id(s1) == id(s2)):
print "Same"
else:
print "Different"
Realmente no veo la necesidad, ya que un módulo con funciones (y no una clase) serviría como un singleton. Todas sus variables se vincularían al módulo, que de todos modos no podría ser instanciado repetidamente.
Si desea usar una clase, no hay forma de crear clases privadas o constructores privados en Python, por lo que no puede protegerse contra múltiples instancias, excepto por convención en el uso de su API. Todavía pondría métodos en un módulo y consideraría el módulo como el singleton.
Siendo relativamente nuevo en Python, no estoy seguro de cuál es el idioma más común, pero lo más simple que se me ocurre es usar un módulo en lugar de una clase. Lo que habrían sido los métodos de instancia en su clase se convierten en funciones en el módulo y cualquier dato se convierte en variables en el módulo en lugar de en miembros de la clase. Sospecho que este es el enfoque pythonic para resolver el tipo de problema para el que la gente usa singletons.
Si realmente desea una clase de singleton, hay una implementación razonable descrita en el primer hit en Google para "Python singleton", específicamente:
class Singleton:
__single = None
def __init__( self ):
if Singleton.__single:
raise Singleton.__single
Singleton.__single = self
Eso parece hacer el truco.
También hay algunos artículos interesantes en el blog de Pruebas de Google, donde se explica por qué singleton es / puede ser malo y es un anti-patrón:
Un enfoque ligeramente diferente para implementar el singleton en Python es el patrón de Borg de Alex Martelli (empleado de Google y genio de Python).
class Borg:
__shared_state = {}
def __init__(self):
self.__dict__ = self.__shared_state
Entonces, en lugar de forzar a todas las instancias a tener la misma identidad, comparten el estado.
Vea esta implementación desde PEP318 , implementando el patrón singleton con un decorador:
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
El patrón Singleton implementado con Python es cortesía de ActiveState.
Parece que el truco es colocar la clase que se supone que solo tiene una instancia dentro de otra clase.
Medio hermano de Singleton
Estoy completamente de acuerdo con staale y dejo aquí una muestra de cómo crear un hermanastro de singleton:
class void:pass
a = void();
a.__class__ = Singleton
a
informará ahora que es de la misma clase que Singleton, incluso si no lo parece. Así que los singletons que usan clases complicadas terminan dependiendo de que no nos metemos mucho con ellos.
Siendo así, podemos tener el mismo efecto y usar cosas más simples como una variable o un módulo. Aún así, si queremos usar clases para mayor claridad y porque en Python una clase es un objeto , entonces ya tenemos el objeto (no una instancia, pero haremos lo mismo).
class Singleton:
def __new__(cls): raise AssertionError # Singletons can''t have instances
Ahí tenemos un buen error de aserción si intentamos crear una instancia, y podemos almacenar en derivaciones miembros estáticos y hacer cambios en ellos en tiempo de ejecución (me encanta Python). Este objeto es tan bueno como el de otros medio hermanos (todavía puedes crearlos si lo deseas), sin embargo, tenderá a correr más rápido debido a la simplicidad.
class Singeltone(type):
instances = dict()
def __call__(cls, *args, **kwargs):
if cls.__name__ not in Singeltone.instances:
Singeltone.instances[cls.__name__] = type.__call__(cls, *args, **kwargs)
return Singeltone.instances[cls.__name__]
class Test(object):
__metaclass__ = Singeltone
inst0 = Test()
inst1 = Test()
print(id(inst1) == id(inst0))
class Singleton(object[,...]):
staticVar1 = None
staticVar2 = None
def __init__(self):
if self.__class__.staticVar1==None :
# create class instance variable for instantiation of class
# assign class instance variable values to class static variables
else:
# assign class static variable values to class instance variables