HOWTO · Python

Singleton-Entwurfsmuster in Python

Dieses Tutorial demonstriert die Implementierung des Singleton-Entwurfsmusters in Python.

Auf dieser Seite

Ein Singleton beschränkt eine Klasse innerhalb eines festgelegten Geltungsbereichs auf eine gemeinsam genutzte Instanz. In Python lässt sich dieses Verhalten mit einem Dekorator, einer Basisklasse, einer Metaklasse oder einem Modulzustand nachbilden. Jeder Ansatz verändert die Objekterzeugung; keiner gewährleistet automatisch Thread-Sicherheit oder ein gemeinsames Objekt für getrennte Prozesse. Die Beispiele zeigen Identitätsprüfungen, damit Sie anhand der Verwendung Ihrer Klasse den passenden Ansatz auswählen können.

So funktioniert ein Singleton in Python

Normalerweise erzeugt jeder Aufruf einer Klasse ein neues Objekt. Ein Singleton ändert diesen Erzeugungsweg, sodass wiederholte Aufrufe dasselbe Objekt zurückgeben. Die Garantie gilt üblicherweise pro Klasse und pro Python-Interpreter; das bedeutet nicht, dass jedes Programm oder jeder Prozess dasselbe globale Objekt sieht. Mit first is second prüfen die Beispiele, ob zwei Namen auf dasselbe Objekt verweisen.

Einen Dekorator für ein Singleton verwenden

Ein Dekorator kann eine Klasse durch eine Funktion ersetzen, die die erste Instanz zwischenspeichert. Das ist kompakt und hält den Zwischenspeicher nahe an der dekorierten Klasse. Der Nachteil: Der öffentliche Name verweist nun auf die Wrapper-Funktion statt auf die ursprüngliche Klasse. Operationen auf Klassenebene, etwa der Aufruf einer Klassenmethode über Sample, funktionieren möglicherweise nicht mehr wie erwartet. Auch Werkzeuge zur Klasseninspektion sehen dadurch eine veränderte Schnittstelle.

Führen Sie dieses Beispiel aus. Die unveränderten Zeichenfolgen im Code, darunter shared und ignored after construction, bleiben Englisch.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

def singleton(class_):
    instances = {}

    def get_instance(*args, **kwargs):
        if class_ not in instances:
            instances[class_] = class_(*args, **kwargs)
        return instances[class_]

    return get_instance


@singleton
class Sample:
    def __init__(self, name="shared"):
        self.name = name


first = Sample()
second = Sample("ignored after construction")
print(first is second)
print(second.name)

Die bereitgestellte Laufzeitprüfung war erfolgreich. Der erste Aufruf erzeugt die Instanz; spätere Aufrufe geben sie zurück, sodass nachfolgende Konstruktorargumente sie nicht erneut initialisieren. Die Ausgabe bestätigt, dass beide Namen auf dasselbe Objekt verweisen und dessen Name weiterhin shared lautet. Diese Zeichenfolge im Code und in der Ausgabe bleibt Englisch.

Eine Basisklasse für ein Singleton verwenden

Eine Basisklasse kann die Instanzerzeugung in __new__ zentralisieren, das vor __init__ ausgeführt wird. Speichern Sie Instanzen nach konkreter Unterklasse, damit verschiedene Unterklassen nicht versehentlich dasselbe Objekt verwenden. Da Python nach der Rückgabe von __new__ weiterhin __init__ aufruft, wird bei jeder Konstruktion initialisiert. Sorgen Sie dafür, dass die Initialisierung wiederholbar ist, oder schützen Sie sie, falls eine erneute Ausführung den Zustand überschreiben würde.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

class SingletonBase:
    _instances = {}

    def __new__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__new__(cls)
        return cls._instances[cls]


class Cache(SingletonBase):
    def __init__(self):
        if not hasattr(self, "values"):
            self.values = {}


first = Cache()
second = Cache()
print(first is second)
print(second.values)

Die Schutzbedingung verhindert, dass ein erneuter Aufruf von __init__ das vorhandene Wörterbuch ersetzt. Lösungen mit einer Basisklasse lassen sich unter Umständen nur schwer mit anderem Vererbungsverhalten kombinieren, insbesondere wenn mehrere Basisklassen die Objekterzeugung anpassen. Berücksichtigen Sie diesen Aufwand, bevor Sie Singleton-Verhalten in eine wiederverwendbare Klassenhierarchie aufnehmen.

Eine Metaklasse für ein Singleton verwenden

Eine Metaklasse steuert die Erzeugung von Klassen. Diese Form in Python 3 belässt die deklarierte Klasse als Klasse und speichert Instanzen für jede Klasse, die diese Metaklasse verwendet, getrennt. Ihre Methode __call__ wird ausgeführt, wenn die Klasse aufgerufen wird.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

class SingletonMeta(type):
    _instances = {}

    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super().__call__(*args, **kwargs)
        return cls._instances[cls]


class Service(metaclass=SingletonMeta):
    def __init__(self, endpoint="local"):
        self.endpoint = endpoint


first = Service()
second = Service("another endpoint")
print(first is second)
print(second.endpoint)

Dieser Ansatz erhält Operationen auf Klassenebene besser als ein Dekorator, der eine Funktion zurückgibt. Allerdings kann eine Metaklasse mit einer anderen Metaklasse kollidieren, die eine Basisklasse benötigt. Die Argumente des ersten Aufrufs initialisieren die zwischengespeicherte Instanz; spätere Aufrufe geben sie zurück, ohne __init__ erneut aufzurufen. Die hier gezeigte Deklaration metaclass=... ist Syntax für Python 3; die ältere Form für Python 2 ist keine aktuelle Empfehlung.

Ein Modul als Singleton verwenden

Python speichert importierte Module innerhalb eines Interpreters in sys.modules. Wird dasselbe Modul unter demselben Namen importiert, erhalten Aufrufende daher dasselbe Modulobjekt und denselben gemeinsam genutzten Modulzustand. Das ist oft die einfachste Wahl, wenn die gemeinsam genutzte Ressource eher aus einer Gruppe von Funktionen und Zustandsdaten als aus einer Instanz mit einer Klassenschnittstelle besteht.

Der Code und die angezeigten Bezeichnungen oder Ausgaben in diesem Abschnitt bleiben auf Englisch.

import importlib

first = importlib.import_module("__main__")
second = importlib.import_module("__main__")
first.shared_value = "available through the module"
print(first is second)
print(second.shared_value)

Diese kurze Demonstration importiert das laufende Modul unter seinem Standardnamen. In einer Anwendung legen Sie den gemeinsam genutzten Zustand und die Funktionen in ein eigenes Modul und sorgen dafür, dass alle Verbraucher dieses Modul einheitlich importieren. Die Zeichenfolge available through the module im unveränderten Code und in der Ausgabe bleibt Englisch. Die Modulzwischenspeicherung gilt jeweils innerhalb eines Interpreters: Getrennte Prozesse haben getrennte Modulobjekte, und ein erneutes Laden des Moduls kann seinen Code nochmals ausführen.

Einen Ansatz wählen und seine Grenzen verstehen

Verwenden Sie Modulzustand, wenn Aufrufende gemeinsam genutzte Funktionen oder Konfiguration benötigen, eine Metaklasse, wenn Sie die Objekterzeugung steuern und zugleich eine Klasse beibehalten möchten, und einen Dekorator, wenn dessen Wrapper-Schnittstelle ausreicht. Eine Basisklasse macht das Verhalten durch Vererbung wiederverwendbar, bringt jedoch Einschränkungen bei Initialisierung und Vererbung mit sich. Lässt sich das Problem durch ein gewöhnliches Objekt lösen, das per Dependency Injection übergeben wird, ist das oft leichter zu testen und nachzuvollziehen.

Diese Beispiele zeigen das Verhalten wiederholter Konstruktionen nur bei gewöhnlicher Verwendung in einem einzelnen Thread. Gleichzeitige erste Aufrufe können zu Wettläufen führen, sofern die Erzeugung nicht synchronisiert wird; außerdem hat jeder Prozess oder Interpreter seinen eigenen Zwischenspeicher. Für Ressourcen, die über Threads, Prozesse oder Rechner hinweg koordiniert werden müssen, sollten Sie eine explizite Synchronisierung oder einen gemeinsam genutzten Dienst verwenden. Entscheiden Sie sich nur dann für ein Singleton, wenn eine gemeinsam genutzte Instanz innerhalb des vorgesehenen Geltungsbereichs tatsächlich erforderlich ist.