Creazione di un singleton in Python


946

Questa domanda non è per la discussione se il modello di progettazione singleton sia desiderabile o meno, sia un anti-modello o per qualsiasi guerra religiosa, ma per discutere di come questo modello sia meglio implementato in Python in un modo che sia il più pitonico. In questo caso, definisco "la maggior parte dei pitoni" per indicare che segue il "principio del minimo stupore" .

Ho più classi che diventerebbero singoli (il mio caso d'uso è per un logger, ma questo non è importante). Non desidero ingombrare diverse classi con l'aggiunta di gumph quando posso semplicemente ereditare o decorare.

I migliori metodi:


Metodo 1: un decoratore

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

Professionisti

  • I decoratori sono additivi in ​​un modo spesso più intuitivo dell'eredità multipla.

Contro

  • Mentre gli oggetti creati usando MyClass () sarebbero veri e propri oggetti singleton, MyClass stesso è una funzione, non una classe, quindi non è possibile richiamare i metodi di classe da essa. Anche per m = MyClass(); n = MyClass(); o = type(n)();alloram == n && m != o && n != o

Metodo 2: una classe di 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

Professionisti

  • È una vera classe

Contro

  • Eredità multipla - eugh! __new__potrebbe essere sovrascritto durante l'ereditarietà da una seconda classe di base? Bisogna pensare più del necessario.

Metodo 3: una metaclasse

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

Professionisti

  • È una vera classe
  • Copre automaticamente l'ereditarietà
  • Usi __metaclass__per il suo scopo (e me ne sono reso conto)

Contro

  • Ci sono?

Metodo 4: decoratore che restituisce una classe con lo stesso nome

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

Professionisti

  • È una vera classe
  • Copre automaticamente l'ereditarietà

Contro

  • Non esiste un sovraccarico per la creazione di ogni nuova classe? Qui stiamo creando due classi per ogni classe che desideriamo creare un singleton. Mentre questo va bene nel mio caso, temo che questo non possa ridimensionarsi. Naturalmente c'è una questione di dibattito sul fatto se debba essere troppo facile ridimensionare questo schema ...
  • Qual è il punto _sealeddell'attributo
  • Non è possibile chiamare metodi con lo stesso nome su classi base usando super()perché faranno ricorso. Ciò significa che non è possibile personalizzare __new__e non è possibile sottoclassare una classe che deve essere richiamata __init__.

Metodo 5: un modulo

un file del modulo singleton.py

Professionisti

  • Semplice è meglio di complesso

Contro


12
Altre tre tecniche: utilizzare invece un modulo (spesso - generalmente, penso - questo è uno schema più appropriato per Python ma dipende un po 'da cosa ci stai facendo); crea una singola istanza e gestiscila invece ( foo.xo se insisti Foo.xinvece di Foo().x); usa gli attributi di classe e i metodi static / class ( Foo.x).
Chris Morgan,

10
@ChrisMorgan: se utilizzerai solo metodi di classe / statici, non preoccuparti di creare una classe, davvero.
Cat Plus Plus,

2
@Cat: L'effetto è simile, tuttavia i motivi alla base della creazione di una variabile globale possono essere praticamente qualsiasi cosa, incluso non conoscerne meglio. Perché si crea un singleton? Se devi chiedere non dovresti essere qui. Questa spiegazione non è solo più pitonica, ma rende la manutenzione molto più semplice. Sì, i singleton sono zucchero sintattico per i globi, ma poi le lezioni sono zucchero sintattico per un sacco di cose sgradevoli e non credo che nessuno ti dirà che stai sempre meglio senza di loro.
theheadofabroom il

14
@BiggAl: i Singleton non sono Pythonic, non importa come li implementi. Nella migliore delle ipotesi sono un segno di un design imperfetto.
Cat Plus Plus,

8
Il sentimento anti-signletons è la programmazione di culto cargo nel peggiore dei casi. Lo stesso vale per le persone che ascoltano (pochi si sono preoccupati di leggere effettivamente) "Le dichiarazioni di Goto sono considerate dannose" e pensano che le goto siano un segno di cattivo codice indipendentemente dal contesto.
Hejazzman,

Risposte:


658

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 ifdichiarazione 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. Loggernel codice sopra sarà di tipo class 'your_module.Singleton', proprio come la (sola) istanza di Loggersarà 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 structin 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 Singletonclasse 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. _instancesdeve 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.


5
No, i singoli non sono mai buoni. La registrazione potrebbe essere un buon candidato per essere globale (per quanto terribile), ma certamente non singleton.
Cat Plus Plus,

11
Guarda googletesting.blogspot.com/2008/08/… . È generalmente anti-singleton (per una buona ragione) ma ha una buona spiegazione del perché i singleton e i singleton immutabili senza effetti collaterali non hanno gli stessi problemi, se stai attento. Lo citerò un po 'alla fine del mio post.
AGF

4
Il mio problema con i singoli è la stupida premessa di "solo un'istanza". Questo e tonnellate di problemi di sicurezza del thread. E nascondersi delle dipendenze. I globi sono cattivi e i singoli sono solo globi con più problemi.
Cat Plus Plus,

4
@Cat Ci sono ottimi usi per i single. Un'istanza pigra dei moduli hardware (specialmente nelle applicazioni a thread singolo) è uno di questi (ma esistono anche singleton thread-safe).
Paul Manta,

3
@Alcott __new__in una metaclasse è quando la classe è nuova - quando è definita, non quando l' istanza sarebbe nuova. Chiamare la classe ( MyClass()) è l'operazione che si desidera sovrascrivere, non la definizione della classe. Se vuoi davvero capire come funziona Python, la cosa migliore che puoi fare (oltre a continuare ad usarlo) è leggere docs.python.org/reference/datamodel.html . Un buon riferimento ai metaclassi è eli.thegreenplace.net/2011/08/14/python-metaclasses-by-example . Un buon articolo sui singoli è la serie dal blog di Google che ho collegato in questa risposta.
AGF

91
class Foo(object):
     pass

some_global_variable = Foo()

I moduli vengono importati una sola volta, tutto il resto è troppo pensante. Non usare i singoli e cercare di non usare i globali.


14
perché hai detto "Non usare i singoli"? Qualche ragione?
Alcott,

3
Questo non funzionerà se il singleton deve essere decapato. Usando l'esempio che hai dato:s = some_global_variable; str = pickle.dumps(s); s1 = pickle.loads(str); print s is s1; # False
dividebyzero

4
@dividebyzero: l' isoperatore verifica l'uguaglianza del puntatore. Sarei piuttosto sorpreso --- al punto di chiamarlo un bug --- se pickle.loadsrestituito un riferimento a un oggetto preesistente piuttosto che un riferimento a uno appena creato. Pertanto, testare se s is s1non ti dice nulla sull'idoneità dell'uso dei moduli come singoli.
Jonas Kölker,

1
@ JonasKölker lo pickle.loads()fa già, ad esempio per i casi di boole NoneType. pickle.loads(pickle.dumps(False)) is FalsereseTrue
Dan Passaro

2
@ leo-the-manic: punto giusto; Tuttavia, questo è solo un effetto collaterale di Python internato gli oggetti True, Falsee None, e non ha nulla a che fare con il codice dietro pickle.loads. Inoltre, è sicuro fare solo per oggetti di sola lettura. Se pickle.loadsrestituisse un riferimento a un oggetto modificabile già esistente , come un modulo, sarebbe un bug. (E quindi sostengo la mia implicazione secondo cui l'esempio di codice di dividebyzero non dimostra nulla.)
Jonas Kölker

69

Usa un modulo. Viene importato una sola volta. Definisci alcune variabili globali in esso - saranno gli "attributi" di singleton. Aggiungi alcune funzioni: i "metodi" del singleton.


11
Quindi quello con cui finisci è ... Non una lezione. Non puoi usarlo come classe, non puoi basare altre classi su di esso, usi la sintassi di importazione e all'improvviso perdi tutti i benefici di OOP ...
theheadofabroom

16
se puoi basare altre classi su di esso, allora potrebbe non essere un singleton. potresti creare una della classe derivata, ma anche una della classe base, ma anche la classe derivata è un membro della base e ne hai due, quale dovresti usare?
SingleNegationElimination

Questo non funziona su tutti i moduli. Nel mio modulo "principale" ho impostato un valore. Quindi lo faccio riferimento in un altro modulo e il suo null. Sospiro.
Paul Kenjora,

1
@PaulKenjora Devi avere un errore nel tuo codice. Se si definisce una variabile globale in un modulo, quando si accede da un altro modulo dovrebbe avere il valore.
Warvariuc,

Ha il valore ma quando lo cambio non viene conservato. Ho finito per usare una classe con proprietà in un modulo che funziona. I tipi semplici come globali non funzionavano per me (hanno perso valori non appena l'ambito è cambiato).
Paul Kenjora,

29

Probabilmente non avrai mai bisogno di un singleton in Python. Basta definire tutti i dati e le funzioni in un modulo e hai un singleton di fatto.

Se davvero devi assolutamente avere una lezione singleton, allora andrei con:

class My_Singleton(object):
    def foo(self):
        pass

my_singleton = My_Singleton()

Usare:

from mysingleton import my_singleton
my_singleton.foo()

dove mysingleton.py è il tuo nome file in cui è definito My_Singleton. Questo funziona perché dopo la prima volta che un file viene importato, Python non riesegue il codice.


4
Principalmente vero, ma a volte non è abbastanza. Ad esempio, ho un progetto con la necessità di registrare le istanze di molte classi a livello di DEBUG. Ho bisogno di opzioni della riga di comando analizzate all'avvio al fine di impostare il livello di registrazione specificato dall'utente prima che tali classi vengano istanziate. Le istanze a livello di modulo lo rendono problematico. È possibile che io possa strutturare attentamente l'app in modo che tutte queste classi non vengano importate fino al completamento dell'elaborazione della CLI, ma la struttura naturale della mia app è più importante dell'adesione dogmatica ai "singleton sono cattivi", poiché possono essere fatto abbastanza chiaramente.
CryingCyclops,

se dovessi testare il tuo codice durante l'applicazione di patch su my_singleton, sarebbe possibile? poiché questo my_singleton potrebbe essere istanziato in qualche altro modulo.
Naveen,

@Naveen - my_singleton è un singolo oggetto. Se lo "rattoppi", quel cambiamento influenzerà tutti i riferimenti futuri, anche in altri moduli.
Alan Dyke,

16

Ecco un one-liner per te:

singleton = lambda c: c()

Ecco come lo usi:

@singleton
class wat(object):
    def __init__(self): self.x = 1
    def get_x(self): return self.x

assert wat.get_x() == 1

Il tuo oggetto viene istanziato avidamente. Questo può o meno essere quello che vuoi.


5
-1. Questo è molto brutto e sciocco. Non si definisce una classe da utilizzare come oggetto. Non puoi più accedere alla classe senza essere molto brutto come type(wat)o wat.__class__. Se vuoi davvero raggiungere questo obiettivo, definisci meglio la classe e istanziala immediatamente, non c'è bisogno di sbagliare con il decoratore.
0xc0de,

2
Perché hai bisogno di sapere usare la classe di un singleton? Basta usare l'oggetto singleton ..
Tolli,

1
Non è un modello singleton , quindi la funzione IMO dovrebbe essere chiamata diversamente.
GingerPlusPlus,

8
Wikipedia: "il modello singleton è un modello di progettazione che limita l'istanza di una classe a un oggetto". Direi che la mia soluzione fa proprio questo. Okay, immagino si possa fare wat2 = type(wat)(), ma questo è Python, siamo tutti adulti consenzienti e tutto il resto. Non puoi garantire che ci sarà solo un'istanza, ma puoi garantire che se le persone ne fanno una seconda, sembrerà brutta e, se sono persone rispettabili e oneste, come un segnale di avvertimento per loro. Cosa mi sto perdendo?
Jonas Kölker,


3

Ecco la mia implementazione di singoli. Tutto quello che devi fare è decorare la classe; per ottenere il singleton, è quindi necessario utilizzare il Instancemetodo Ecco un esempio:

   @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

Ed ecco il codice:

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. 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.

    Limitations: The decorated class cannot be inherited from.

    """

    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)

2
Non è vero singleton. SingletonList = Singleton(list).Instance(); print(SingletonList is type(SingletonList)())dovrebbe stampare Truein singleton vero; con il tuo codice stampeFalse
GingerPlusPlus

@GingerPlusPlus Ero a conoscenza di alcuni limiti, ma non di quello che hai indicato. Grazie per averlo menzionato. Sfortunatamente, al momento non ho tempo per dilungarmi su una soluzione a questo.
Paul Manta,

2

Il metodo 3 sembra essere molto pulito, ma se vuoi che il tuo programma sia eseguito in Python 2 e Python 3 , non funziona. Anche la protezione delle varianti separate con i test per la versione di Python ha esito negativo, poiché la versione di Python 3 genera un errore di sintassi in Python 2.

Grazie a Mike Watkins: http://mikewatkins.ca/2008/11/29/python-2-and-3-metaclasses/ . Se vuoi che il programma funzioni in Python 2 e Python 3, devi fare qualcosa del tipo:

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]

MC = Singleton('MC', (object), {})

class MyClass(MC):
    pass    # Code for the class implementation

Presumo che "oggetto" nell'assegnazione debba essere sostituito con "BaseClass", ma non l'ho provato (ho provato il codice come illustrato).


sicuramente questa non è una metaclasse - in python3 per usare una metaclasse per costruire MyClass faresticlass MyClass(metaclass=Singleton)
theheadofabroom

Il link mikewatkins.ca è (effettivamente) interrotto.
Peter Mortensen,

1

Bene, oltre a concordare con il suggerimento generale di Pythonic sull'avere globale a livello di modulo, che ne dici di questo:

def singleton(class_):
    class class_w(class_):
        _instance = None
        def __new__(class2, *args, **kwargs):
            if class_w._instance is None:
                class_w._instance = super(class_w, class2).__new__(class2, *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(object):
    def __init__(self, text):
        print text
    @classmethod
    def name(class_):
        print class_.__name__

x = MyClass(111)
x.name()
y = MyClass(222)
print id(x) == id(y)

L'output è:

111     # the __init__ is called only on the 1st time
MyClass # the __name__ is preserved
True    # this is actually the same instance

Qual è il punto _sealeddell'attributo? Per quanto vedo questo non fa nulla? Qualcosa mi sta prendendo in giro per questo che dice che non dovrebbe funzionare bene ... Eseguirò alcuni test comparativi entro questa settimana.
theheadofabroom il

_sealed assicura che il tuo init venga eseguito una sola volta; Non vedo alcun motivo per cui dovrebbe funzionare peggio del normale decoratore simile a una funzione - la funzione viene eseguita solo una volta per classe e restituisce una nuova classe ereditata
Guardia

A proposito, la tua modifica contiene schede che interrompono i rientri Dici anche "stiamo creando 2 classi" - vuoi dire che stiamo creando "1 classe extra"?
Guardia il

Sì, una lezione in più è ciò che intendevo. Intendo includere elementi __init__da chiamare ogni volta che vengono inizializzati. Solo un semplice "Essere inizializzati in class.method". per quanto riguarda il rientro - hai usato tabulazioni e spazi - ho risolto gran parte di esso, ma sembra che ne abbia perso uno se vuoi ottenerlo (controlla il registro delle modifiche)
theheadofabroom,

re init : ovviamente dipende da te, ho appena provato a imitare il comportamento singleton in altre lingue, in cui il codice del costruttore (che non è esattamente init , ma molto vicino nel suo senso) viene chiamato solo una volta se vuoi che init sia chiamato ogni volta, basta uccidere tutti i riferimenti a _sealed re spazi / tab - beh, allora il mio emacs deve essere riparato. comunque, sopra è la versione corretta
Guardia

1

Cosa ne pensi di questo:

def singleton(cls):
    instance=cls()
    cls.__new__ = cls.__call__= lambda cls: instance
    cls.__init__ = lambda self: None
    return instance

Usalo come decoratore in una classe che dovrebbe essere un singleton. Come questo:

@singleton
class MySingleton:
    #....

Questo è simile al singleton = lambda c: c()decoratore in un'altra risposta. Come l'altra soluzione, l'unica istanza ha il nome della classe ( MySingleton). Tuttavia, con questa soluzione è ancora possibile "creare" istanze (effettivamente ottenere l'unica istanza) dalla classe, facendo MySingleton(). Ti impedisce anche di creare ulteriori istanze facendo type(MySingleton)()(che restituisce anche la stessa istanza).


Non si definisce una classe per usarla come oggetto.
0xc0de,

1
Ogni volta che chiami type(MySingleton)(), MySingleton.__init__()viene chiamato e l'oggetto viene inizializzato più volte; puoi sistemarlo scrivendo cls.__init__ = lambda self: passnel tuo singleton. Inoltre, l'override cls.__call__sembra inutile e persino dannoso: __call__definito in questo contesto viene utilizzato quando si chiama MySingleton(any, list, of, arguments), non quando si chiama type(MySingleton)(any, list, of, arguments).
GingerPlusPlus

@GingerPlusPlus, Grazie per aver sottolineato che __init__()viene chiamato di nuovo quando lo fai type(MySingleton)(). La soluzione che hai proposto (aggiungendo cls.__init__ = lambda self: pass) fornisce un errore di sintassi, poiché l'ultima parte dell'espressione lambda deve essere un'espressione, non un'istruzione. Tuttavia, aggiungendo cls.__init__ = lambda self: Nonelavori, quindi l'ho aggiunto alla mia risposta.
Tolli,

1
@GingerPlusPlus, Per quanto riguarda l'uso di __call__. la mia intenzione era di fare entrambe le cose type(MySingleton)()e MySingleton()restituire l'istanza. Quindi sta facendo quello che volevo. Puoi pensare a MySingleton come al tipo di singleton o all'istanza del singleton (o entrambi).
Tolli,

1

Lancio il mio sul ring. È un semplice decoratore.

from abc import ABC

def singleton(real_cls):

    class SingletonFactory(ABC):

        instance = None

        def __new__(cls, *args, **kwargs):
            if not cls.instance:
                cls.instance = real_cls(*args, **kwargs)
            return cls.instance

    SingletonFactory.register(real_cls)
    return SingletonFactory

# Usage
@singleton
class YourClass:
    ...  # Your normal implementation, no special requirements.

Vantaggi Penso che abbia rispetto ad alcune delle altre soluzioni:

  • È chiaro e conciso (ai miei occhi; D).
  • La sua azione è completamente incapsulata. Non è necessario modificare nulla sull'implementazione di YourClass. Ciò include la necessità di non utilizzare una metaclasse per la tua classe (nota che la metaclasse sopra è in fabbrica, non la classe "reale").
  • Non si basa su patch per scimmie.
  • È trasparente per i chiamanti:
    • I chiamanti continuano a importare YourClass, sembra una classe (perché lo è) e la usano normalmente. Non è necessario adattare i chiamanti a una funzione di fabbrica.
    • Ciò che YourClass()crea un'istanza è ancora una vera istanza di YourClasste implementata, non un proxy di alcun tipo, quindi nessuna possibilità di effetti collaterali derivanti da ciò.
    • isinstance(instance, YourClass) e operazioni simili funzionano ancora come previsto (anche se questo bit richiede abc, quindi preclude Python <2.6).

Mi viene in mente un aspetto negativo: i metodi di classe e i metodi statici della classe reale non sono richiamabili in modo trasparente tramite la classe di fabbrica che lo nasconde. L'ho usato abbastanza raramente che non mi è mai capitato di incontrare quella necessità, ma sarebbe facilmente rettificato utilizzando una metaclasse personalizzata in fabbrica che implementa __getattr__()per delegare l'accesso agli attributi all-ish alla classe reale.

Un modello correlato che in realtà ho trovato più utile (non che sto dicendo che questo tipo di cose sono richieste molto spesso) è un modello "Unico" in cui l'istanza della classe con gli stessi argomenti porta a recuperare la stessa istanza. Vale a dire un "singleton per argomenti". Quanto sopra si adatta a questo bene e diventa ancora più conciso:

def unique(real_cls):

    class UniqueFactory(ABC):

        @functools.lru_cache(None)  # Handy for 3.2+, but use any memoization decorator you like
        def __new__(cls, *args, **kwargs):
            return real_cls(*args, **kwargs)

    UniqueFactory.register(real_cls)
    return UniqueFactory

Detto questo, concordo con il consiglio generale che se pensi di aver bisogno di una di queste cose, probabilmente dovresti fermarti un attimo e chiederti se davvero lo fai. Il 99% delle volte, YAGNI.


1
  • Se si desidera avere un numero multiplo di istanze della stessa classe, ma solo se args o kwargs sono diversi, si può usare questo
  • Ex.
    1. Se si dispone di una serialcomunicazione di gestione delle classi e per creare un'istanza si desidera inviare la porta seriale come argomento, l'approccio tradizionale non funzionerà
    2. Utilizzando i suddetti decoratori, è possibile creare più istanze della classe se gli argomenti sono diversi.
    3. Per gli stessi argomenti, il decoratore restituirà la stessa istanza che è già stata creata.
>>> from decorators import singleton
>>>
>>> @singleton
... class A:
...     def __init__(self, *args, **kwargs):
...         pass
...
>>>
>>> a = A(name='Siddhesh')
>>> b = A(name='Siddhesh', lname='Sathe')
>>> c = A(name='Siddhesh', lname='Sathe')
>>> a is b  # has to be different
False
>>> b is c  # has to be same
True
>>>

0

Codice basato sulla risposta di Tolli .

#decorator, modyfies new_cls
def _singleton(new_cls):
    instance = new_cls()                                              #2
    def new(cls):
        if isinstance(instance, cls):                                 #4
            return instance
        else:
            raise TypeError("I can only return instance of {}, caller wanted {}".format(new_cls, cls))
    new_cls.__new__  = new                                            #3
    new_cls.__init__ = lambda self: None                              #5
    return new_cls


#decorator, creates new class
def singleton(cls):
    new_cls = type('singleton({})'.format(cls.__name__), (cls,), {} ) #1
    return _singleton(new_cls)


#metaclass
def meta_singleton(name, bases, attrs):
    new_cls = type(name, bases, attrs)                                #1
    return _singleton(new_cls)

Spiegazione:

  1. Crea una nuova classe, ereditando da un dato cls
    (non si modifica clsnel caso qualcuno voglia per esempio singleton(list))

  2. Crea istanza. Prima di eseguire __new__l' override è così semplice.

  3. Ora, quando abbiamo facilmente creato un'istanza, sostituisce __new__usando il metodo definito poco fa.
  4. La funzione ritorna instancesolo quando è ciò che il chiamante si aspetta, altrimenti genera TypeError.
    La condizione non è soddisfatta quando qualcuno tenta di ereditare dalla classe decorata.

  5. Se __new__()restituisce un'istanza di cls, il __init__()metodo della nuova istanza verrà invocato come __init__(self[, ...]), dove self è la nuova istanza e gli argomenti rimanenti sono gli stessi a cui sono stati passati __new__().

    instanceè già inizializzato, quindi la funzione sostituisce __init__con la funzione che non fa nulla.

Guardalo lavorare online


0

È leggermente simile alla risposta di fab ma non è esattamente la stessa.

Il contratto singleton non richiede che possiamo chiamare più volte il costruttore. Come un singleton dovrebbe essere creato una volta e una volta sola, non dovrebbe essere visto per essere creato solo una volta? "Spoofing" il costruttore compromette probabilmente la leggibilità.

Quindi il mio suggerimento è proprio questo:

class Elvis():
    def __init__(self):
        if hasattr(self.__class__, 'instance'):
            raise Exception()
        self.__class__.instance = self
        # initialisation code...

    @staticmethod
    def the():
        if hasattr(Elvis, 'instance'):
            return Elvis.instance
        return Elvis()

Questo non esclude l'uso del costruttore o del campo instanceda parte del codice utente:

if Elvis() is King.instance:

... se sai per certo che Elvisnon è ancora stato creato, e che Kingè stato.

Ma incoraggia gli utenti a utilizzare il themetodo universalmente:

Elvis.the().leave(Building.the())

Per completare questo, puoi anche ignorare __delattr__()un'eccezione se si tenta di eliminare instancee ignorare in __del__()modo che generi un'eccezione (a meno che non sappiamo che il programma sta finendo ...)

Ulteriori miglioramenti


I miei ringraziamenti a coloro che mi hanno aiutato con i commenti e le modifiche, di cui altri sono i benvenuti. Mentre uso Jython, questo dovrebbe funzionare in modo più generale ed essere thread-safe.

try:
    # This is jython-specific
    from synchronize import make_synchronized
except ImportError:
    # This should work across different python implementations
    def make_synchronized(func):
        import threading
        func.__lock__ = threading.Lock()

        def synced_func(*args, **kws):
            with func.__lock__:
                return func(*args, **kws)

        return synced_func

class Elvis(object): # NB must be subclass of object to use __new__
    instance = None

    @classmethod
    @make_synchronized
    def __new__(cls, *args, **kwargs):
        if cls.instance is not None:
            raise Exception()
        cls.instance = object.__new__(cls, *args, **kwargs)
        return cls.instance

    def __init__(self):
        pass
        # initialisation code...

    @classmethod
    @make_synchronized
    def the(cls):
        if cls.instance is not None:
            return cls.instance
        return cls()

Punti da notare:

  1. Se non esegui la sottoclasse dall'oggetto in python2.x otterrai una classe vecchio stile che non utilizza __new__
  2. Quando decorare __new__è necessario decorare con @classmethod o__new__ sarà un metodo di istanza non
  3. Questo potrebbe eventualmente essere migliorato mediante l'uso di una metaclasse, in quanto ciò consentirebbe di creare theuna proprietà a livello di classe, possibilmente rinominandola ininstance

Mentre questa è un'interpretazione leggermente diversa del modello singleton, sono abbastanza sicuro che sia ancora valido, anche se potrei essere tentato di usare __new__ piuttosto che __init__, dato che agisce puramente sugli attributi di classe e questo impedisce loro di essere brevemente una seconda istanza. La differenza quindi tra questo e il metodo 2 è se il tentativo di inizializzare più di una volta restituisce la singola istanza o solleva un'eccezione. Penso di essere felice di soddisfare il modello singleton, uno è più facile da usare, mentre l'altro è più esplicito che si tratta di un singleton.
theheadofabroom,

Ovviamente l'uso del nome della classe __init__impedisce la sottoclasse, ma sebbene ciò renda le cose più facili, non è richiesto
theheadofabroom

Grazie ... ah sì, una seconda istanza momentanea prima che venga generata l'eccezione. L'ho modificato in __init__modo che, si spera, questo sia subclassabile ...
mike rodent,

Fantastico, thepotrebbe probabilmente trarre vantaggio dall'essere un metodo di classe per ragioni simili
theheadofabroom,

sì hai ragione. Quindi puoi avere un singleton di sottoclasse SuperElvis e (per esempio) un singleton di sottoclasse ImaginaryElvis ... e possono coesistere. Vedi pensieri aggiuntivi. Sentiti libero di migliorare il mio codice.
mike rodent,

0

Una fodera (non sono orgoglioso, ma fa il lavoro):

class Myclass:
  def __init__(self):
      # do your stuff
      globals()[type(self).__name__] = lambda: self # singletonify

Questo potrebbe fare il lavoro purché la tua classe non sia stata importata in nessun altro modulo ...
Aran-Fey

Vero. Se riesci a pagare il costo dell'iscrizione alla classe, puoi eseguire Myclass () immediatamente dopo la definizione della classe per evitarlo.
polvoazul,

0

Se non hai bisogno di inizializzazione lenta dell'istanza di Singleton, allora quanto segue dovrebbe essere facile e sicuro per i thread:

class A:
    instance = None
    # Methods and variables of the class/object A follow
A.instance = A()

In questo modo Aè un singleton inizializzato all'importazione del modulo.


0

Forse ho frainteso il modello singleton ma la mia soluzione è questa semplice e pragmatica (pitonica?). Questo codice soddisfa due obiettivi

  1. Rendi l'istanza di Fooaccessibile ovunque (globale).
  2. FooPuò esistere solo un'istanza di .

Questo è il codice

#!/usr/bin/env python3

class Foo:
    me = None

    def __init__(self):
        if Foo.me != None:
            raise Exception('Instance of Foo still exists!')

        Foo.me = self


if __name__ == '__main__':
    Foo()
    Foo()

Produzione

Traceback (most recent call last):
  File "./x.py", line 15, in <module>
    Foo()
  File "./x.py", line 8, in __init__
    raise Exception('Instance of Foo still exists!')
Exception: Instance of Foo still exists!

0

Dopo aver lottato con questo per un po 'di tempo alla fine ho pensato a quanto segue, in modo che l'oggetto config venisse caricato solo una volta, quando richiamato da moduli separati. La metaclasse consente di archiviare un'istanza di classe globale nel dict incorporato, che al momento sembra essere il modo più accurato per memorizzare un programma globale adeguato.

import builtins

# -----------------------------------------------------------------------------
# So..... you would expect that a class would be "global" in scope, however
#   when different modules use this,
#   EACH ONE effectively has its own class namespace.  
#   In order to get around this, we use a metaclass to intercept
#   "new" and provide the "truly global metaclass instance" if it already exists

class MetaConfig(type):
    def __new__(cls, name, bases, dct):
        try:
            class_inst = builtins.CONFIG_singleton

        except AttributeError:
            class_inst = super().__new__(cls, name, bases, dct)
            builtins.CONFIG_singleton = class_inst
            class_inst.do_load()

        return class_inst

# -----------------------------------------------------------------------------

class Config(metaclass=MetaConfig):

    config_attr = None

    @classmethod
    def do_load(cls):
        ...<load-cfg-from-file>...

-1

Non ricordo dove ho trovato questa soluzione, ma trovo che sia la più "elegante" dal mio punto di vista non esperto di Python:

class SomeSingleton(dict):
    __instance__ = None
    def __new__(cls, *args,**kwargs):
        if SomeSingleton.__instance__ is None:
            SomeSingleton.__instance__ = dict.__new__(cls)
        return SomeSingleton.__instance__

    def __init__(self):
        pass

    def some_func(self,arg):
        pass

Perché mi piace questo? Nessun decoratore, nessuna meta classe, nessuna eredità multipla ... e se decidi di non voler più essere un Singleton, elimina semplicemente il __new__metodo. Dato che sono nuovo di Python (e OOP in generale), mi aspetto che qualcuno mi spiegherà chiaramente perché questo è un approccio terribile?


2
perché questo è un approccio terribile? quando vuoi creare un'altra classe singleton, devi copiare e incollare il file __new__. Non ripeterti .
GingerPlusPlus,

Inoltre, perché il tuo nuovo prende *argse **kwargs, e quindi non fa nulla con loro? Loro passaggio in dict.__new__questo modo: dict.__new__(cls, *args, **kwargs).
GingerPlusPlus

Questo chiamerà il __init__metodo ogni volta che viene chiamata la classe. Se il tuo __init__metodo ha effettivamente fatto qualcosa, noteresti il ​​problema. Ogni volta che lo fai SomeSingleton(), lo stato del tuo singleton viene ripristinato dal __init__metodo.
Aran-Fey,

-2

Questo è il mio modo preferito di implementare i singoli:

class Test(object):
    obj = None

    def __init__(self):
        if Test.obj is not None:
            raise Exception('A Test Singleton instance already exists')
        # Initialization code here

    @classmethod
    def get_instance(cls):
        if cls.obj is None:
            cls.obj = Test()
        return cls.obj

    @classmethod
    def custom_method(cls):
        obj = cls.get_instance()
        # Custom Code here

1
Questo non è strettamente un singleton in quanto consente l'esistenza di più di un'istanza della classe. Un miglioramento consisterebbe nel rendere impossibile l'inizializzazione della classe e far sì che tutti i metodi di classe agiscano sugli attributi della classe
theheadofabroom

-2

Questa risposta non è probabilmente quello che stai cercando. Volevo un singleton, nel senso che solo quell'oggetto aveva la sua identità, a confronto. Nel mio caso veniva utilizzato come valore Sentinel . Per cui la risposta è molto semplice, crea qualsiasi oggetto mything = object()e per natura di Python, solo quella cosa avrà la sua identità.

#!python
MyNone = object()  # The singleton

for item in my_list:
    if item is MyNone:  # An Example identity comparison
        raise StopIteration

Ho appreso che i moduli possono essere effettivamente importati più volte, in tal caso si tratta solo di un singleton locale, che non è in realtà un singleton a qualsiasi titolo.
ThorSummoner,

Puoi approfondire come importare più volte un modulo? L'unica volta che ho visto che è quando si verifica un'eccezione mentre il modulo è caricato, l'utente può ancora caricare il modulo in un secondo momento, ma gli effetti collaterali si sono già verificati, quindi alcune azioni potrebbero essere eseguite una seconda volta.
slitta,

Una volta che un modulo è stato caricato completamente, non vedo un modo per far funzionare nuovamente questo modulo, se non costringendo l'interprete a farlo usando evalo importlib.reload.
slitta,

-3

Questa soluzione provoca un certo inquinamento dello spazio dei nomi a livello di modulo (tre definizioni anziché solo una), ma trovo che sia facile da seguire.

Mi piacerebbe poter scrivere qualcosa del genere (inizializzazione pigra), ma sfortunatamente le classi non sono disponibili nel corpo delle proprie definizioni.

# wouldn't it be nice if we could do this?
class Foo(object):
    instance = None

    def __new__(cls):
        if cls.instance is None:
            cls.instance = object()
            cls.instance.__class__ = Foo
        return cls.instance

Poiché ciò non è possibile, possiamo interrompere l'inizializzazione e l'istanza statica in

Inizializzazione desiderosa:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        return foo_instance


foo_instance = FooMaker()
foo_instance.__class__ = Foo

Inizializzazione lenta:

Inizializzazione desiderosa:

import random


class FooMaker(object):
    def __init__(self, *args):
        self._count = random.random()
        self._args = args


class Foo(object):
    def __new__(self):
        global foo_instance
        if foo_instance is None:
            foo_instance = FooMaker()
        return foo_instance


foo_instance = None
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.