Python: pensar en un módulo y sus variables como un singleton – ¿Un enfoque limpio?

Me gustaría implementar algún tipo de patrón de singleton en mi progtwig Python. Estaba pensando en hacerlo sin usar clases; es decir, me gustaría poner todas las funciones y variables relacionadas con el singleton dentro de un módulo y considerarlo como un singleton real.

Por ejemplo, digamos que esto debe estar en el archivo ‘singleton_module.py’:

# singleton_module.py # Singleton-related variables foo = 'blah' bar = 'stuff' # Functions that process the above variables def work(some_parameter): global foo, bar if some_parameter: bar = ... else: foo = ... 

Luego, el rest del progtwig (es decir, otros módulos) usaría este singleton así:

 # another_module.py import singleton_module # process the singleton variables, # which changes them across the entire program singleton_module.work(...) # freely access the singleton variables # (at least for reading) print singleton_module.foo 

Esto me pareció una buena idea, porque se ve bastante limpio en los módulos que usan el singleton.

Sin embargo, todas estas declaraciones tediosas “globales” en el módulo de singleton son feas. Ocurren en cada función que procesa las variables relacionadas con el singleton. Eso no es mucho en este ejemplo en particular, pero cuando tienes más de 10 variables para administrar en varias funciones, no es bonito.

Además, esto es bastante propenso a errores si se le olvidan las declaraciones globales: se crearán variables locales a la función, y las variables del módulo no se cambiarán, ¡eso no es lo que desea!

Entonces, ¿esto sería considerado como limpio? ¿Existe un enfoque similar al mío que logre acabar con el desorden ‘global’?

¿O es simplemente que no es el camino a seguir?

Una alternativa común a usar un módulo como singleton es el patrón Borg de Alex Martelli:

 class Borg: __shared_state = {} def __init__(self): self.__dict__ = self.__shared_state # and whatever else you want in your class -- that's all! 

Puede haber varias instancias de esta clase, pero todas comparten el mismo estado.

Tal vez pueda poner todas las variables en un dict global, y puede usar directamente el dict en sus funciones sin “global”.

 # Singleton-related variables my_globals = {'foo': 'blah', 'bar':'stuff'} # Functions that process the above variables def work(some_parameter): if some_parameter: my_globals['bar'] = ... else: my_globals['foo'] = ... 

por qué puedes hacerlo de esta manera son los ámbitos y los espacios de nombres de Python .

Un enfoque para implementar un patrón de singleton con Python también puede ser:

el __init()__ singleton __init()__ una excepción si ya existe una instancia de la clase. Más precisamente, la clase tiene un miembro _single . Si este miembro es diferente de None , se None excepción.

 class Singleton: __single = None def __init__( self ): if Singleton.__single: raise Singleton.__single Singleton.__single = self 

Se podría argumentar que manejar la creación de la instancia de singleton con excepciones tampoco es muy claro. Podemos ocultar los detalles de la implementación con un método handle() como en

 def Handle( x = Singleton ): try: single = x() except Singleton, s: single = s return single 

este método Handle() es muy similar a lo que sería una implementación en C ++ del patrón Singleton. Podríamos tener en la clase Singleton el handle()

 Singleton& Singleton::Handle() { if( !psingle ) { psingle = new Singleton; } return *psingle; } 

devolviendo una nueva instancia de Singleton o una referencia a la instancia única existente de la clase Singleton .

Manejando toda la jerarquía.

Si las clases Single1 y Single2 derivan de Singleton , existe una instancia única de Singleton través de una de las clases derivadas. Esto se puede verificar con esto:

 >>> child = S2( 'singlething' ) >>> junior = Handle( S1) >>> junior.name() 'singlething' 

De manera similar a la sugerencia de “patrón” de Sven, puede mantener todos los datos de su estado en una clase, sin crear instancias de la clase. Este método utiliza clases de nuevo estilo, creo.

Este método podría incluso adaptarse al patrón Borg, con la advertencia de que modificar los miembros del estado de las instancias de la clase requeriría acceder al atributo __class__ de la instancia ( instance.__class__.foo = 'z' lugar de instance.foo = 'z' , aunque también stateclass.foo = 'z' hacer stateclass.foo = 'z' ).

 class State: # in some versions of Python, may need to be "class State():" or "class State(object):" __slots__ = [] # prevents additional attributes from being added to instances and same-named attributes from shadowing the class's attributes foo = 'x' bar = 'y' @classmethod def work(cls, spam): print(cls.foo, spam, cls.bar) 

Tenga en cuenta que las modificaciones a los atributos de la clase se reflejarán en las instancias de la clase incluso después de la creación de instancias. Esto incluye agregar nuevos atributos y eliminar los existentes, lo que podría tener algunos efectos interesantes y posiblemente útiles (aunque también puedo ver cómo eso podría causar problemas en algunos casos). Pruébalo tú mismo.

Aprovechando la respuesta de WillYang y dándole un paso más para la limpieza: defina una clase simple para mantener su diccionario global para que sea más fácil hacer referencia:

 class struct(dict): def __init__(self, **kwargs): dict.__init__(self, kwargs) self.__dict__ = self g = struct(var1=None, var2=None) def func(): g.var1 = dict() g.var3 = 10 g["var4"] = [1, 2] print(g["var3"]) print(g.var4) 

Al igual que antes de poner lo que quieras en g pero ahora está super limpio. 🙂

Para un Singleton legítimo:

 class SingletonMeta(type): __classes = {} # protect against defining class with the same name def __new__(cls, cls_name, cls_ancestors, cls_dict): if cls_name in cls.__classes: return cls.__classes[cls_name] type_instance = super(SingletonMeta, cls).__new__(cls, cls_name, cls_ancestors, cls_dict) # pass 'type' instead of 'cls' if you dont want SingletonMeta's attributes reflected in the class return type_instance() # call __init__ class Singleton: __metaclass__ = SingletonMeta # define __init__ however you want __call__(self, *args, *kwargs): print 'hi!' 

Para ver que realmente es un singleton, intente crear una instancia de esta clase, o cualquier clase que herede de ella.

 singleton = Singleton() # prints "hi!"