Diese Frage dient nicht der Diskussion darüber, ob die Singleton-Entwurfsmuster ist wünschenswert, ist ein Anti-Muster oder für irgendwelche Religionskriege, aber zu diskutieren, wie dieses Muster am besten in Python so implementiert wird, dass es am pythonischsten ist. In diesem Fall definiere ich „am pythonischsten“ so, dass es dem „Prinzip des geringsten Erstaunens“ folgt..
Ich habe mehrere Klassen, die zu Singletons werden würden (mein Anwendungsfall ist für einen Logger, aber das ist nicht wichtig). Ich möchte nicht mehrere Klassen mit zusätzlichem Kauderwelsch überladen, wenn ich einfach erben oder dekorieren kann.
Beste Methoden:
Methode 1: Ein Dekorateur
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
Vorteile
- Dekorateure sind auf eine Weise additiv, die oft intuitiver ist als Mehrfachvererbung.
Nachteile
-
Während Objekte erstellt mit
MyClass()
wären echte Singleton-Objekte,MyClass
selbst ist eine Funktion, keine Klasse, daher können Sie keine Klassenmethoden von ihr aufrufen. Auch fürx = MyClass(); y = MyClass(); t = type(n)();
dann x == y
aber x != t && y != t
Methode 2: Eine Basisklasse
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
Vorteile
- Es ist eine wahre Klasse
Nachteile
- Mehrfachvererbung – pfui!
__new__
könnte bei der Vererbung von einer zweiten Basisklasse überschrieben werden? Man muss mehr denken als nötig.
Methode 3: Eine Metaklasse
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
Vorteile
- Es ist eine wahre Klasse
- Deckt Vererbung automatisch magisch ab
- Verwendet
__metaclass__
für den eigentlichen Zweck (und hat mich darauf aufmerksam gemacht)
Nachteile
- Sind da welche?
Methode 4: Decorator, der eine Klasse mit demselben Namen zurückgibt
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
Vorteile
- Es ist eine wahre Klasse
- Deckt Vererbung automatisch magisch ab
Nachteile
- Gibt es keinen Overhead für die Erstellung jeder neuen Klasse? Hier erstellen wir zwei Klassen für jede Klasse, die wir zu einem Singleton machen möchten. Während dies in meinem Fall in Ordnung ist, mache ich mir Sorgen, dass dies möglicherweise nicht skaliert. Natürlich lässt sich darüber streiten, ob es nicht zu einfach sein soll, dieses Muster zu skalieren …
- Was ist der Sinn der
_sealed
Attribut - Es können keine Methoden mit demselben Namen für Basisklassen mit aufgerufen werden
super()
denn sie werden wiederkehren. Dies bedeutet, dass Sie nicht anpassen können__new__
und kann keine Klasse unterteilen, die Sie aufrufen müssen__init__
.
Methode 5: ein Modul
eine Moduldatei singleton.py
Vorteile
- Einfach ist besser als komplex
Nachteile
- Nicht faul instanziiert
Drei weitere Techniken: Verwenden Sie stattdessen ein Modul (häufig – im Allgemeinen denke ich – ist dies ein geeigneteres Muster für Python, aber es hängt ein wenig davon ab, was Sie damit machen); Erstellen Sie eine einzelne Instanz und behandeln Sie sie stattdessen (
foo.x
oder wenn Sie darauf bestehenFoo.x
Anstatt vonFoo().x
); Verwenden Sie Klassenattribute und statische/Klassenmethoden (Foo.x
).– Chris Morgan
20. Juli 2011 um 10:55 Uhr
@ChrisMorgan: Wenn Sie nur Klassen-/statische Methoden verwenden, machen Sie sich nicht wirklich die Mühe, eine Klasse zu erstellen.
– Katze Plus Plus
20. Juli 2011 um 10:57 Uhr
@Cat: Der Effekt ist ähnlich, aber die Gründe für das Erstellen einer globalen Variablen können so ziemlich alles sein, einschließlich Nichtwissen. Warum erstellt man ein Singleton? Wenn Sie fragen müssen, sollten Sie nicht hier sein. Diese Explizitheit ist nicht nur pythonischer, sondern macht die Wartung viel einfacher. Ja, Singletons sind syntaktischer Zucker für Globals, aber Klassen sind syntaktischer Zucker für eine ganze Reihe unansehnlicher Dinge, und ich glaube nicht, dass Ihnen irgendjemand sagen wird, dass Sie ohne sie immer besser dran sind.
– der Kopf des Besens
20. Juli 2011 um 14:22 Uhr
Die Anti-Signletons-Stimmung ist Cargo-Cult-Programmierung in ihrer schlimmsten Form. Das Gleiche gilt für Leute, die hören (wenige haben sich die Mühe gemacht, es tatsächlich zu lesen) “Goto-Anweisung als schädlich angesehen” und denken, dass Gotos unabhängig vom Kontext ein Zeichen für schlechten Code sind.
– Hejazzman
30. November 2015 um 13:31 Uhr
Hallo, danke für deinen ausführlichen Beitrag. Ich bin ziemlich neu in der Musterprogrammierung und in Python, und ich bin überrascht, dass, obwohl Methode 2 den meisten bekannt scheint (sie ist überall), kaum jemand erwähnt, dass, obwohl nur ein Objekt erstellt wird, init__() wird jedes Mal aufgerufen, wenn Singleton() oder MyClass() irgendwo verwendet werden. Ich habe es nicht versucht, aber AFAIK gilt dies auch für alle anderen Methoden. Dies scheint bei der Implementierung eines Singletons kaum wünschenswert zu sein, oder übersehe ich etwas? Die Lösung besteht natürlich darin, ein Attribut zu setzen, um die Ausführung von __init zu vermeiden zweimal. Nur neugierig
– chrisvp
4. September 2016 um 11:52 Uhr