Usa una metaclasse
Consiglierei il Metodo n. 2 , ma è meglio usare una metaclasse piuttosto che una classe base. Ecco un'implementazione di esempio:
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 in Python3
class Logger(metaclass=Singleton):
pass
Se si desidera eseguire __init__
ogni volta che viene chiamata la classe, aggiungere
else:
cls._instances[cls].__init__(*args, **kwargs)
alla if
dichiarazione in Singleton.__call__
.
Qualche parola sui metaclassi. Una metaclasse è la classe di una classe ; cioè, una classe è un'istanza della sua metaclasse . In Python trovi la metaclasse di un oggetto type(obj)
. Le normali classi di nuovo stile sono di tipo type
. Logger
nel codice sopra sarà di tipo class 'your_module.Singleton'
, proprio come la (sola) istanza di Logger
sarà di tipo class 'your_module.Logger'
. Quando si chiama logger con Logger()
, Python prima chiede al metaclasse di Logger
, Singleton
, cosa fare, permettendo la creazione di istanza può essere anticipata. Questo processo è lo stesso di Python che chiede a una classe cosa fare chiamando facendo __getattr__
riferimento a uno dei suoi attributi myclass.attribute
.
Una metaclasse decide essenzialmente cosa significa la definizione di una classe e come implementare tale definizione. Vedi ad esempio http://code.activestate.com/recipes/498149/ , che essenzialmente ricrea gli stili C struct
in Python usando metaclassi. Il thread Quali sono alcuni casi d'uso (concreti) per i metaclassi? fornisce anche alcuni esempi, in genere sembrano essere collegati alla programmazione dichiarativa, in particolare quelli utilizzati negli ORM.
In questa situazione, se si utilizza il metodo n. 2 e una sottoclasse definisce un __new__
metodo, verrà eseguito ogni volta che si chiama SubClassOfSingleton()
, poiché è responsabile della chiamata del metodo che restituisce l'istanza memorizzata. Con una metaclasse, verrà chiamata una sola volta , quando viene creata l'unica istanza. Vuoi personalizzare ciò che significa chiamare la classe , che è deciso dal suo tipo.
In generale, ha senso utilizzare una metaclasse per implementare un singleton. Un singleton è speciale perché viene creato una sola volta e una metaclasse è il modo in cui personalizzi la creazione di una classe . L'uso di una metaclasse offre un maggiore controllo nel caso in cui sia necessario personalizzare le definizioni della classe singleton in altri modi.
I tuoi singleton non avranno bisogno di ereditarietà multipla (perché la metaclasse non è una classe base), ma per le sottoclassi della classe creata che usano l'ereditarietà multipla, devi assicurarti che la classe singleton sia la prima / più a sinistra con una metaclasse che ridefinisce __call__
È molto improbabile che ciò costituisca un problema. Il dict dell'istanza non si trova nello spazio dei nomi dell'istanza, quindi non lo sovrascriverà accidentalmente.
Sentirai anche che il modello singleton viola il "Principio di responsabilità singola" - ogni classe dovrebbe fare solo una cosa . In questo modo non devi preoccuparti di rovinare una cosa che il codice fa se devi cambiarne un'altra, perché sono separate e incapsulate. L'implementazione della metaclasse supera questo test . La metaclasse è responsabile dell'applicazione del modello e la classe creata e le sottoclassi non devono essere consapevoli del fatto che sono singoli . Il metodo n. 1 fallisce questo test, come hai notato con "MyClass stesso è una funzione, non una classe, quindi non puoi chiamare i metodi di classe da essa."
Versione compatibile di Python 2 e 3
Scrivere qualcosa che funzioni sia in Python2 che in 3 richiede l'uso di uno schema leggermente più complicato. Poiché metaclassi sono solitamente sottoclassi di tipo type
, è possibile utilizzare uno per creare dinamicamente una classe base intermedia in fase di esecuzione con esso come metaclasse e quindi utilizzare che come baseclass del pubblico Singleton
classe base. È più difficile da spiegare che da fare, come illustrato di seguito:
# 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 aspetto ironico di questo approccio è che utilizza la sottoclasse per implementare una metaclasse. Un possibile vantaggio è che, a differenza di una metaclasse pura, isinstance(inst, Singleton)
tornerà True
.
Correzioni
Su un altro argomento, probabilmente lo hai già notato, ma l'implementazione della classe base nel tuo post originale è errata. _instances
deve essere referenziato sulla classe , è necessario utilizzare super()
o si sta ricorrendo , ed __new__
è in realtà un metodo statico a cui si deve passare la classe , non un metodo di classe, poiché la classe effettiva non è stata ancora creata quando è chiamato. Tutte queste cose saranno vere anche per un'implementazione della metaclasse.
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()
Decoratore che restituisce una classe
Inizialmente stavo scrivendo un commento ma era troppo lungo, quindi lo aggiungerò qui. Il metodo n. 4 è migliore dell'altra versione di Decorator, ma è più codice del necessario per un singleton e non è così chiaro cosa faccia.
I problemi principali derivano dalla classe essendo la propria classe base. Primo, non è strano che una classe sia una sottoclasse di una classe quasi identica con lo stesso nome che esiste solo nel suo __class__
attributo? Questo significa anche che non è possibile definire tutti i metodi che chiamano il metodo con lo stesso nome sulla loro classe di base con super()
perché saranno recurse. Ciò significa che la tua classe non può essere personalizzata __new__
e non può derivare da alcuna classe che deve essere __init__
richiamata.
Quando utilizzare il modello singleton
Il tuo caso d'uso è uno dei migliori esempi di voler usare un singleton. Dici in uno dei commenti "Per me il logging è sempre sembrato un candidato naturale per Singletons". Hai assolutamente ragione .
Quando le persone dicono che i singoli sono cattivi, la ragione più comune è che sono stati condivisi impliciti . Mentre con le variabili globali e le importazioni dei moduli di livello superiore sono espliciti stati condivisi, altri oggetti che vengono passati vengono generalmente istanziati. Questo è un buon punto, con due eccezioni .
Il primo, e quello che viene menzionato in vari luoghi, è quando i singoli sono costanti . L'uso di costanti globali, in particolare gli enumerati, è ampiamente accettato e considerato sano perché, indipendentemente da ciò, nessuno degli utenti può confonderli con qualsiasi altro utente . Questo è altrettanto vero per un singleton costante.
La seconda eccezione, che viene menzionata meno, è l'opposto: quando il singleton è solo un sink di dati , non un'origine dati (direttamente o indirettamente). Questo è il motivo per cui i logger si sentono un uso "naturale" per i single. Poiché i vari utenti non stanno modificando i logger in modo che gli altri utenti si preoccupino, non esiste davvero uno stato condiviso . Ciò annulla l'argomento principale rispetto al modello singleton e li rende una scelta ragionevole a causa della loro facilità d'uso per l'attività.
Ecco una citazione da http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
Ora, c'è un tipo di Singleton che è OK. Questo è un singleton in cui tutti gli oggetti raggiungibili sono immutabili. Se tutti gli oggetti sono immutabili di Singleton non ha uno stato globale, poiché tutto è costante. Ma è così facile trasformare questo tipo di singleton in mutevole, è una pendenza molto scivolosa. Pertanto, sono contro questi Singleton, non perché siano cattivi, ma perché è molto facile per loro andare male. (Come nota a margine, l'enumerazione Java è proprio questo tipo di singoli punti. Finché non si inserisce lo stato nell'enumerazione, si va bene, quindi per favore non farlo.)
L'altro tipo di Singleton, che sono semi-accettabili sono quelli che non influiscono sull'esecuzione del codice, non hanno "effetti collaterali". La registrazione è un esempio perfetto. È caricato con Singletons e stato globale. È accettabile (in quanto non ti farà male) perché l'applicazione non si comporta in modo diverso indipendentemente dal fatto che un determinato logger sia abilitato o meno. Le informazioni qui scorre in un modo: dall'applicazione al logger. Anche se i logger sono di stato globale poiché nessuna informazione scorre dai logger all'applicazione, i logger sono accettabili. Dovresti comunque iniettare il tuo logger se vuoi che il tuo test affermi che qualcosa viene registrato, ma in generale i logger non sono dannosi nonostante siano pieni di stato.
foo.x
o se insistiFoo.x
invece diFoo().x
); usa gli attributi di classe e i metodi static / class (Foo.x
).