Using Singletons in Python
Che cos'è un singleton?
Un singleton è un oggetto di cui c'è solo uno, semanticamente, in memoria.
Singoletti incorporati
None
è un singleton frequentemente usato. NotImplemented
è un altro, molto meno usato. Possiamo testare l'identità di un singolo:
>>> maybe_None = None
E poi
maybe_None is None
restituisce True
.
Creazione di un singleton semantico
Puoi semplicemente usare un'istanza di oggetto per crearne una che non ha bisogno di funzionalità nello spazio dei nomi globale di un modulo (tutto maiuscolo, perché è una costante globale):
SINGLETON = object()
Fai attenzione a non sovrascrivere l'oggetto nello spazio dei nomi globale. Se gli dai un buon nome, dovrebbe essere ovvio quando qualcuno sta facendo qualcosa di sbagliato con esso.
Questo è simile all'esempio che hai dato. Tuttavia, non farei ciò che stai facendo se la creazione dell'oggetto è costosa - rende così costoso importare il modulo, anche se non vuoi usare l'oggetto.
Perché non desideri un singleton globale all'importazione
Dove non vuoi che un singleton creato durante l'importazione sia in qualsiasi momento desideri importare il modulo in modo economico.
Vuoi che i tuoi moduli siano poco costosi da importare quando si genera dinamicamente la documentazione, per esempio.
Un altro caso in cui dovresti preferire le importazioni poco costose sono quelle per l'esecuzione di un non test.
Inoltre, il metodo che usi non supporta l'ereditarietà.
Quindi ti mostrerò alcuni modi per ovviare a questo problema.
Un'istanza singleton
Dove vuoi avere una sola istanza di una classe, puoi farlo in vari modi:
personalizza __new__
e utilizza un attributo di classe, instance
class Controller(object):
instance = None
def __new__(cls):
if cls.instance is not None:
return cls.instance
else:
inst = cls.instance = super(Controller, cls).__new__()
return inst
E nota che questo supporta l'ereditarietà.
memoize a factory function
È possibile creare un attributo di funzione per memoizzare la funzione
def get_controller():
if hasattr(get_controller, 'instance'):
return get_controller.instance
else:
inst = get_controller.instance = Controller()
return inst
Potresti anche usare il modulo o lo spazio dei nomi della classe per mantenere l'istanza in - poiché anche la funzione e il modulo dovrebbero essere entrambi singleton.
Oppure usa lru_cache (dato che get_controller non accetta argomenti) per memorizzarlo.
from functools import lru_cache
@lru_cache
def get_controller():
return Controller()
Si noti che se si aggiungono argomenti a get_controller
, lru_cache restituirà un singleton per ogni combinazione univoca di argomenti, fino alla sua dimensione cache (teoricamente), dopo di che dimentica le istanze, quindi fai attenzione con questo se aggiungi argomenti - probabilmente meglio per implementare il tuo comportamento, come ho fatto con l'attributo function.
Questo può supportare l'ereditarietà, per esempio.
@lru_cache
def get_controller(controller_type):
if issubclass(controller_type, Controller):
return controller_type()
else:
raise TypeError('must supply a Controller type')