Verwenden Sie eine Metaklasse
Ich würde Methode 2 empfehlen , aber Sie sind besser dran, eine Metaklasse als eine Basisklasse zu verwenden. Hier ist eine Beispielimplementierung:
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
Oder in Python3
class Logger(metaclass=Singleton):
pass
Wenn Sie __init__
jedes Mal ausführen möchten, wenn die Klasse aufgerufen wird, fügen Sie hinzu
else:
cls._instances[cls].__init__(*args, **kwargs)
zur if
Aussage in Singleton.__call__
.
Ein paar Worte zu Metaklassen. Eine Metaklasse ist die Klasse einer Klasse ; Das heißt, eine Klasse ist eine Instanz ihrer Metaklasse . Sie finden die Metaklasse eines Objekts in Python mit type(obj)
. Normale Klassen neuen Stils sind vom Typ type
. Logger
im obigen Code wird vom Typ sein class 'your_module.Singleton'
, genauso wie die (einzige) Instanz von Logger
vom Typ sein wird class 'your_module.Logger'
. Wenn Sie Logger rufen mit Logger()
, fragt Python zuerst die Metaklasse von Logger
, Singleton
, was zu tun ist, so dass beispielsweise die Schaffung vorbelegt werden. Dieser Vorgang ist der gleiche wie bei Python, bei dem eine Klasse gefragt wird, was zu tun ist, indem sie aufruft, __getattr__
wenn Sie dabei auf eines ihrer Attribute verweisen myclass.attribute
.
Eine Metaklasse entscheidet im Wesentlichen, was die Definition einer Klasse bedeutet und wie diese Definition implementiert wird. Siehe zum Beispiel http://code.activestate.com/recipes/498149/ , das im Wesentlichen C-Stile struct
in Python mithilfe von Metaklassen neu erstellt. Der Thread Was sind einige (konkrete) Anwendungsfälle für Metaklassen? liefert auch einige Beispiele, sie scheinen im Allgemeinen mit deklarativer Programmierung in Zusammenhang zu stehen, insbesondere wie sie in ORMs verwendet werden.
Wenn Sie in dieser Situation Ihre Methode 2 verwenden und eine Unterklasse eine __new__
Methode definiert , wird diese bei jedem Aufruf ausgeführt, SubClassOfSingleton()
da sie für den Aufruf der Methode verantwortlich ist, die die gespeicherte Instanz zurückgibt. Bei einer Metaklasse wird sie nur einmal aufgerufen , wenn die einzige Instanz erstellt wird. Sie möchten anpassen, was es bedeutet, die Klasse aufzurufen , was durch ihren Typ bestimmt wird.
Im Allgemeinen ist es sinnvoll , eine Metaklasse zum Implementieren eines Singletons zu verwenden. Ein Singleton ist etwas Besonderes, da er nur einmal erstellt wird. Mit einer Metaklasse können Sie die Erstellung einer Klasse anpassen . Durch die Verwendung einer Metaklasse erhalten Sie mehr Kontrolle, falls Sie die Definitionen der Singleton-Klassen auf andere Weise anpassen müssen.
Ihre Singletons benötigen keine Mehrfachvererbung (da die Metaklasse keine Basisklasse ist). Für Unterklassen der erstellten Klasse , die Mehrfachvererbung verwenden, müssen Sie jedoch sicherstellen, dass die Singleton-Klasse die erste / ganz links stehende Metaklasse mit einer neu definierten Metaklasse ist __call__
Es ist sehr unwahrscheinlich, dass dies ein Problem darstellt. Das Instanz-Diktat befindet sich nicht im Namespace der Instanz, sodass es nicht versehentlich überschrieben wird.
Sie werden auch hören, dass das Singleton-Muster gegen das "Prinzip der Einzelverantwortung" verstößt - jede Klasse sollte nur eines tun . Auf diese Weise müssen Sie sich nicht darum kümmern, eine Sache des Codes durcheinander zu bringen, wenn Sie eine andere ändern müssen, da sie getrennt und gekapselt sind. Die Metaklassenimplementierung besteht diesen Test . Die Metaklasse ist für die Durchsetzung des Musters verantwortlich , und die erstellten Klassen und Unterklassen müssen nicht wissen, dass es sich um Singletons handelt . Methode Nr. 1 besteht diesen Test nicht, wie Sie mit "MyClass selbst ist eine Funktion, keine Klasse, daher können Sie keine Klassenmethoden daraus aufrufen" festgestellt haben.
Python 2 und 3 kompatible Version
Um etwas zu schreiben, das sowohl in Python2 als auch in Python 3 funktioniert, muss ein etwas komplizierteres Schema verwendet werden. Da in der Regel metaclasses Subklassen von Typ sind type
, dann ist es möglich , eine zu verwenden , um dynamisch eine Zwischenbasisklasse zur Laufzeit mit ihm als seine Metaklasse zu erstellen und verwenden Sie dann , dass als die Basisklasse der öffentlichen Singleton
Basisklasse. Es ist schwieriger zu erklären als zu tun, wie im Folgenden dargestellt:
# 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
Ein ironischer Aspekt dieses Ansatzes ist, dass zur Implementierung einer Metaklasse Unterklassen verwendet werden. Ein möglicher Vorteil ist, dass im Gegensatz zu einer reinen Metaklasse isinstance(inst, Singleton)
zurückkehren wird True
.
Korrekturen
Bei einem anderen Thema haben Sie dies wahrscheinlich bereits bemerkt, aber die Implementierung der Basisklasse in Ihrem ursprünglichen Beitrag ist falsch. _instances
muss auf die Klasse verwiesen werden , muss verwendet werden super()
oder Sie rekursieren und __new__
ist eigentlich eine statische Methode, an die Sie die Klasse übergeben müssen , keine Klassenmethode, da die eigentliche Klasse noch nicht erstellt wurde, als sie erstellt wurde wird genannt. All diese Dinge gelten auch für eine Metaklassenimplementierung.
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()
Dekorateur, der eine Klasse zurückgibt
Ich habe ursprünglich einen Kommentar geschrieben, aber er war zu lang, deshalb füge ich ihn hier hinzu. Methode 4 ist besser als die andere Decorator-Version, aber es ist mehr Code als für einen Singleton erforderlich, und es ist nicht so klar, was es tut.
Die Hauptprobleme ergeben sich daraus, dass die Klasse eine eigene Basisklasse ist. Ist es nicht seltsam, wenn eine Klasse eine Unterklasse einer nahezu identischen Klasse mit demselben Namen ist, die nur in ihrem __class__
Attribut existiert? Das bedeutet auch , dass Sie nicht definieren können alle Methoden, die die Methode mit dem gleichen Namen auf ihrer Basisklasse aufrufen mit , super()
weil sie Rekursion wird. Dies bedeutet, dass Ihre Klasse nicht angepasst werden __new__
kann und nicht von Klassen abgeleitet werden kann, die sie aufrufen __init__
müssen.
Wann wird das Singleton-Muster verwendet?
Ihr Anwendungsfall ist eines der besseren Beispiele für die Verwendung eines Singletons. Sie sagen in einem der Kommentare: "Für mich war das Protokollieren immer ein natürlicher Kandidat für Singletons." Du hast absolut recht .
Wenn Leute sagen, Singletons seien schlecht, ist der häufigste Grund, dass sie einen impliziten gemeinsamen Zustand haben . Während bei globalen Variablen und Modulimporten der obersten Ebene ein expliziter gemeinsamer Status vorliegt, werden andere Objekte, die weitergegeben werden, im Allgemeinen instanziiert. Dies ist mit zwei Ausnahmen ein guter Punkt .
Die erste und eine, die an verschiedenen Stellen erwähnt wird, ist, wenn die Singletons konstant sind . Die Verwendung globaler Konstanten, insbesondere von Aufzählungen, wird allgemein akzeptiert und als vernünftig angesehen, da keiner der Benutzer sie für einen anderen Benutzer durcheinander bringen kann . Dies gilt auch für einen konstanten Singleton.
Die zweite Ausnahme, die weniger erwähnt wird, ist das Gegenteil - wenn der Singleton nur eine Datensenke ist , keine Datenquelle (direkt oder indirekt). Aus diesem Grund fühlen sich Logger wie eine "natürliche" Verwendung für Singletons. Da die verschiedenen Benutzer die Logger nicht so ändern, wie es anderen Benutzern wichtig ist, gibt es keinen wirklich gemeinsam genutzten Status . Dies negiert das Hauptargument gegen das Singleton-Muster und macht sie aufgrund ihrer einfachen Verwendung für die Aufgabe zu einer vernünftigen Wahl .
Hier ist ein Zitat von http://googletesting.blogspot.com/2008/08/root-cause-of-singletons.html :
Nun gibt es eine Art von Singleton, die in Ordnung ist. Das ist ein Singleton, bei dem alle erreichbaren Objekte unveränderlich sind. Wenn alle Objekte unveränderlich sind, hat Singleton keinen globalen Status, da alles konstant ist. Aber es ist so einfach, diese Art von Singleton in einen veränderlichen zu verwandeln, es ist ein sehr rutschiger Hang. Deshalb bin ich auch gegen diese Singletons, nicht weil sie schlecht sind, sondern weil es für sie sehr leicht ist, schlecht zu werden. (Nebenbei bemerkt, Java-Enumeration ist genau diese Art von Singletons. Solange Sie keinen Status in Ihre Enumeration einfügen, sind Sie in Ordnung, also bitte nicht.)
Die andere Art von Singletons, die halbwegs akzeptabel sind, sind solche, die die Ausführung Ihres Codes nicht beeinflussen. Sie haben keine "Nebenwirkungen". Die Protokollierung ist ein perfektes Beispiel. Es ist mit Singletons und globalem Zustand geladen. Dies ist akzeptabel (da es Sie nicht verletzt), da sich Ihre Anwendung nicht anders verhält, unabhängig davon, ob ein bestimmter Logger aktiviert ist oder nicht. Die Informationen hier fließen in eine Richtung: Von Ihrer Anwendung in den Logger. Auch wenn Logger als global eingestuft werden, da keine Informationen von Loggern in Ihre Anwendung fließen, sind Logger akzeptabel. Sie sollten Ihren Logger trotzdem injizieren, wenn Sie möchten, dass Ihr Test bestätigt, dass etwas protokolliert wird, aber im Allgemeinen sind Logger trotz ihres Status nicht schädlich.
foo.x
oder , wenn Sie darauf bestehen ,Foo.x
stattFoo().x
); Verwenden Sie Klassenattribute und statische / Klassenmethoden (Foo.x
).