Das Singleton-Pattern garantiert, dass es nur eine Instanz einer Klasse geben kann. Die repräsentiert in der Regel ein Objekt, das in der Realität nur einmal vorkommt, beispielsweise ein Dateisystem. Python bietet verschiedene Möglichkeiten, um ein Singleton abzubilden.
Die berühmte Gang of Four (GoF [1]) definiert ein Singleton so: “Ensure a class only has a single instance, and provide a global point of access to it.” Singletons können nützlich sein, um Objekte abzubilden, die in der Realität nur einmal auftreten können, wie ein Dateisystem oder einen Printer-Spooler. Bei Singletons handelt es sich um globale Objekte, die versuchen, die Nachteile von globalen Variablen zu vermeiden, und trotzdem programmweit direkt verfügbar sind. Wird ein Singleton an verschiedenen Stellen in einem Programm modifiziert, kann es allerdings zu unerwarteten Effekten kommen. Python bietet aber Möglichkeiten, um solche Effekte zu unterbinden.
Module sind Singletons
Python bietet von Haus aus schon ein eingebautes Singleton: Module in Python sind natürliche Singletons. Das liegt an der Funktionsweise des Import-Systems. Der Befehl »import module_name« importiert das Modul mit dem Namen module_name. So importiert beispielsweise »import os« das Modul os aus der Standardbibliothek, das viele betriebssystemnahe Funktionen abstrahiert und damit plattformübergreifend zur Verfügung stellt, zum Beispiel das Erstellen von Verzeichnissen oder den Abruf von Metadaten.
Für das Singleton-Verhalten beim komplexen Import-Prozess ist das Caching maßgebend. Beim ersten Import eines Moduls liest Python den Dateiinhalt ein und wandelt ihn in mehreren Schritten in ein Python-Objekt um. Dieses Objekt speichert es dann im Dictionary »sys.modules« mit dem Modulnamen als Schlüssel und dem Modulobjekt als Wert. Beim Import »import os« sieht Python in »sys.modules« nach, findet dort den Eintrag »os« und holt sich das Modulobjekt, ohne den Einleseprozess nochmals zu durchlaufen. Dieses Nachschlagen im Dictionary »sys.modules« läuft um mehrere Größenordnungen schneller ab als das Einlesen beim ersten Import. Jedes Python-Objekt hat einen Referenzzähler, der angibt, wie viele Referenzen auf es zeigen. Bei jedem Import erhöht sich dieser Zähler, und »sys.getrefcount(os)« gibt jedes Mal eine größere Zahl zurück.
Eingebaute Module
Bei sys handelt es sich um ein eingebautes Modul, also eines, das im Python-Interpreter integriert ist. Im Gegensatz zum Modul os, dessen Quelltext in der Datei »os.py« vorliegt, gibt es für sys keine Datei »sys.py«. Beim interaktiven Ausprobieren von »sys.getrefcount(os)« in Systemen wie IPython oder JupyterLab kann der Zähler zwischen zwei Importen durchaus um zwei, drei oder mehr ansteigen, da diese Systeme selbst zusätzliche Referenzen auf Objekte erzeugen.
Listing 1 zeigt eine Singleton-Implementierung, die das Modul-Caching nutzt. Im selben Modul, das die Klasse definiert, entsteht die Instanz »singleton = Singleton(100)«. Der Operator »del« löscht ein Objekt. Das passiert hier direkt nach der Instanziierung. Damit steht die Klasse »Singleton« nicht mehr zur Verfügung. Es kann also nur eine Instanz geben, die mit dem Import »from singleton_module import singleton« an beliebig vielen Stellen im Programm zur bereitsteht.
Listing 1
Singleton mit Modul
class Singleton:
"""Only one instance planned.
"""
# pylint: disable-msg=too-few-public-methods
def __init__(self, value):
self.value = value
singleton = Singleton(100)
del Singleton
Singletons mit <C>__new__<C>
Dieser Ansatz lässt sich aber umgehen, wenn man es denn darauf anlegt. Das Dictionary »sys.modules« ist veränderbar und lässt nicht nur das Hinzufügen von Schlüssel-Wert-Paaren zu, sondern auch das Löschen. So entfernt »del sys.modules[‘singleton_module’]« das Modul »singleton_module« aus dem Dictionary, sodass beim nächsten Import wieder der gesamte Einlesevorgang stattfindet. Damit ist das Singleton kaputt, da beim Import »from module import singleton_module« eine neue Singleton-Instanz entsteht.
Python agiert so dynamisch, dass sich fast immer ein Weg finden lässt, um solche Zwänge wie hier beim Singleton zu umgehen. Das dynamische Neuladen von Modulen kann aber durchaus nützlich sein und gute Dienste leisten, beispielsweise wenn Systeme vom Nutzer erstellten Code zur Laufzeit aktualisieren müssen.
Eine weitere Möglichkeit, ein Singleton umzusetzen, bietet die spezielle Methode »__new__«. Im Vergleich zur speziellen Methode »__init__«, die es bei den meisten Klassen gibt, kommt »__new__« selten vor. Während »__init__« als erstes Argument »self« übergeben bekommt, also eine Referenz auf die Instanz, erhält »__new__« als erstes Argument »cls«, also die Klasse. Während »__init__« nichts (»None«) zurückgeben darf, muss »__new__« die neue Instanz zurückgeben. Obwohl man die Namen für die Argumente frei wählen könnte, haben sich »self« und »cls« als Konvention etabliert.
Listing 2 zeigt eine Singleton-Umsetzung mit »__new__«. Entscheidend ist hier »cls._inst«, das eine Referenz auf die einzige Instanz enthält. Existiert »cls._inst« noch nicht, kommt »__new__« der Elternklasse zum Zug und erzeugt eine Instanz. Beim nächsten Aufruf von »__new__« existiert »cls._inst« bereits, sodass aus dem Aufruf von »cls._inst_« als Ergebnis die bereits bestehende Instanz resultiert.
Die Klasse »OnlyOne« erbt von der Singleton-Klasse und implementiert ihr spezifisches Verhalten, das im Beispiel minimal ausfällt: Die Instanz speichert nur einen Wert mit dem generischen Namen »value«. Wichtig ist die spezielle Methode »__repr__«, die eine Text-Repräsentation einer Instanz zurückgibt. Idealerweise sollte dieser String mit »eval(object_string)« wieder eine Instanz erzeugen. Hier enthält der Objekt-String zusätzlich noch die Objekt-ID, um die Objekte einfacher auseinanderhalten zu können. In Produktionscode ist das typischerweise nicht so.
Die Funktion »create_only_one« in Listing 2 erzeugt zwei Instanzen mit unterschiedlichen Anfangswerten und zeigt deren String-Repräsentation an. Die identische Zahl für die ID beweist, dass es sich bei den vermeintlich zwei Instanzen eigentlich nur um eine handelt. Damit ist das Verhalten eines Singletons umgesetzt.
Listing 2
Singleton mit __new__
class Singleton:
"""Singleton always returning the same instance in `__new__`.
"""
def __new__(cls, *args, **kwargs):
# pylint: disable=unused-argument
if '_inst' not in vars(cls):
cls._inst = super().__new__(cls)
return cls._inst
class OnlyOne(Singleton):
"""Test singleton.
Each instance sets a new value.
"""
def __init__(self, value):
self.value = value
def __repr__(self):
return f'{id(self)}: {self.__class__.__name__}(value={self.value!r})'
def create_only_one():
"""Test singleton.
"""
print('singleton 1:', OnlyOne(10))
print('singleton 2:', OnlyOne(20))
### Aufruf ###
create_only_one()
singleton 1: 4815132688: OnlyOne(value=10)
singleton 2: 4815132688: OnlyOne(value=20)
Durch die zweite Instanziierung mit einem anderen Anfangswert ändert sich auch der Wert von »value« in der ersten Instanz, da es sich ja um zwei Referenzen auf dieselbe Instanz handelt. Das kann durchaus das gewünschte Verhalten sein. Es ergibt sich aber ein gewisser Geistereffekt, da die Änderung von »value« in der ersten Instanz nicht offensichtlich ist.
Listing 3 zeigt eine Variation, bei der man den Wert von »value« nur bei der ersten Instanziierung setzen kann. Beim zweiten Mal ignoriert der Code in »__init__« den Wert von »value«. Damit fällt der Geistereffekt der indirekten Änderung von »value« in der ersten Instanz weg. Hier steuert das Klassenattribut »_first«, ob »value« eine Wirkung hat.
In Listing 3 ist »self.__class__._first« äquivalent zu »OnlyOneInitOnce._first«. Die direkte Nutzung des Klassennamens funktioniert aber nicht bei Vererbung, da »OnlyOneInitOnce._first« nicht auf »_first« der erbenden Klasse zugreift. Mit »self.__class__._first« sucht Python bei der aktuellen (also der erbenden) Klasse nach dem Attribut »_first«.
Listing 3
Singleton mit initialen Argumenten
class OnlyOneInitOnce(OnlyOne):
"""Test singleton.
Subsequent instances cannot set a new value.
"""
_first = True
def __init__(self, value):
if self.__class__._first:
super().__init__(value)
self.__class__._first = False
def create_only_one_init_once():
"""Test singleton that can only be initialized with `value` once.
"""
print('singleton init once 1:', OnlyOneInitOnce(10))
print('singleton init once 2:', OnlyOneInitOnce(20))
### Aufruf ###
create_only_one_init_once()
singleton init once 1: 4920752400: OnlyOneInitOnce(value=10)
singleton init once 2: 4920752400: OnlyOneInitOnce(value=10)
Jetzt hat allerdings der Wert von »value« bei der zweiten Instanziierung keine Wirkung – ein möglicherweise unerwünschtes Verhalten. Listing 4 zeigt eine weitere Variation, die Werte für »value« nur bei der ersten Instanziierung erlaubt. Ab der zweiten Instanziierung wirft die »__init__«-Methode eine Ausnahme, wenn man ihr ein Argument übergibt. Um zwischen den Fällen (a) “ein Argument an »__init__« übergeben” und (b) “kein Argument übergeben” zu unterscheiden, ist ein eindeutiges Objekt nötig. Es hat hier den Namen »SENTINEL« und muss außerhalb der Klasse definiert sein, damit es zur Klassendefinitionszeit verfügbar ist. Per Konvention bestehen globale Variablennamen nur aus Großbuchstaben.
Listing 4
Variation zu Listing 3
SENTINEL = object()
class OnlyOneInitOnceRaise(OnlyOne):
"""Test singleton.
Subsequent instances cannot set a new value.
Values in `__init__` raise an exception
"""
_first = True
def __init__(self, value=SENTINEL):
if self.__class__._first:
value = None if value is SENTINEL else value
super().__init__(value)
self.__class__._first = False
else:
if value is not SENTINEL:
raise TypeError('cannot use arguments in `__init__`')
def create_only_one_init_once_raise():
"""Test singleton that can only be initialized with `value` once.
Raises exception if `value` is given the second time.
"""
print('singleton init once raise 1:', OnlyOneInitOnceRaise(10))
print('singleton init once raise 2:', OnlyOneInitOnceRaise())
try:
OnlyOneInitOnceRaise(20)
except TypeError as err:
print(err)
### Aufruf ###
create_only_one_init_once_raise()
singleton init once raise 1: 4819040848: OnlyOneInitOnceRaise(value=10)
singleton init once raise 2: 4819040848: OnlyOneInitOnceRaise(value=10)
cannot use arguments in `__init__`
Singleton mit Metaklasse
Die Implementierung in Listing 4 verschiebt einen großen Teil der Singleton-Funktionalität in die erbende Klasse. Damit vermischt sich die Singleton-Funktionalität mit der für die Anwendung spezifischen, also der erbenden Klasse. Listing 5 zeigt eine alternative Implementierung mit einer Metaklasse. Sie fällt wesentlich komplexer aus als die Lösung in Listing 4. Der große Vorteil besteht in der Isolierung der gesamten Singleton-Funktionalität in der Metaklasse.
Listing 5
Metaklasse für Singleton
class MetaSingleton(type):
"""Meta class for s singleton """
_singleton_internals_defaults = {
'first': True,
'instance': None,
'new_args_allowed': False
}
def __call__(cls, *args, **kwargs):
"""This called when `ClassName()` is used.
Returns a class instance.
"""
_singleton_internals = '_singleton_internals'
if _singleton_internals not in cls.__dict__:
setattr(cls, _singleton_internals,
cls._singleton_internals_defaults.copy())
if hasattr(cls, '_overide_singleton_internals'):
cls._singleton_internals.update(
cls._overide_singleton_internals)
if cls._singleton_internals['first']:
cls._singleton_internals['first'] = False
instance = object.__new__(cls)
instance.__init__(*args, **kwargs)
cls._singleton_internals['instance'] = instance
else:
if (not cls._singleton_internals['new_args_allowed'] and
(args or kwargs)):
raise TypeError('cannot use arguments in `__init__`')
return cls._singleton_internals['instance']
Metaklassen verhalten sich zu Klassen wie Klassen zu Instanzen. Alle Klassen haben standardmäßig die Metaklasse »type«. Indem Klassen von »type« erben, lassen sich eigene Metaklassen erzeugen. Klassen, die diese neuen Metaklassen beerben, verhalten sich wieder auf eigene Weise. Im Unterschied zu Methoden in Klassen, die als erstes Argument die Instanz erhalten, bekommen Metaklassen als erstes Argument die Klasse. Per Konvention kommt für das erste Argument bei Klassenmethoden der Name »self« zum Einsatz, bei Metaklassenmethoden dagegen der Name »cls«.
Die spezielle Methode »__class__« einer Metaklasse ruft Python auf, wenn die runden Klammern nach dem Klassennamen erscheinen. Somit bewirkt »ClassName(*args, **kwargs)« den Aufruf »__call__(cls, *args, **kwargs)« der Metaklasse von »ClassName«. Durch die Implementierung von »__call__« der Metaklasse lässt sich der Prozess der Erzeugung einer Klasseninstanz vollständig anpassen. Listing 5 zeigt diese Anpassung, um denselben Effekt wie den von Listing 4 zu erreichen.
Das Klassenattribut »__singleton_internals« ist ein Dictionary, das alle für die Steuerung der Instanziierung nötigen Informationen enthält. Wenn die Klasse dieses Attribut noch nicht besitzt, erhält sie eine Kopie des Dictionarys »_singleton_internals_defaults«. Verfügt die Klasse über das Attribut »_overide_singleton_internals«, überschreiben dessen Werte die Vorgabewerte.
Bei der ersten Instanziierung, wenn also »cls._singleton_internals[‘first’]« den Wert »True« hat, erzeugt die Methode eine neue Instanz und speichert diese unter dem Schlüssel »instance« in »_singleton_internals«. Je nach Einstellung von »new_args_allowed« wirft die Methode eine Ausnahme, wenn sie ab dem zweiten Aufruf Argumente erhalten hat. Schlussendlich gibt sie die gespeicherte Instanz zurück.
Die Nutzung dieser Metaklasse zeigt Listing 6. Hier legt »class Singleton(metaclass=MetaSingleton)« die Metaklasse fest. Ansonsten gibt es in der Klasse keinen Code, der sich mit der Singleton-Funktionalität beschäftigt. Das Ergebnis entspricht dem aus Listing 4.
Listing 7 zeigt die Anpassung des Verhaltens mit »_overide_singleton_internals«. Hier erlaubt »new_args_allowed==True« Argumente auch ab der zweiten Instanziierung. Wieder übernimmt die Metaklasse die gesamte Singleton-Funktionalität.
Listing 6
Singleton mit Metaklasse
class Singleton(metaclass=MetaSingleton):
"""Singleton with metaclass."""
def __init__(self, value=None):
self.value = value
def __repr__(self):
return f'{id(self)}: {self.__class__.__name__}(value={self.value!r})'
def create_singleton():
"""Create instances of Singleton."""
print(Singleton(10))
print(Singleton())
try:
Singleton(20)
except TypeError as err:
print(err)
### Aufruf ###
create_singleton()
5088426896: Singleton(value=10)
5088426896: Singleton(value=10)
cannot use arguments in `__init__`
Listing 7
Singleton mit Metaklasse und Option
class SingletonWithNewArgs(Singleton):
"""Singleton with metaclass, no arguments to second instantiation
allowed.
"""
_overide_singleton_internals = {'new_args_allowed': True}
def create_singleton_with_new_args():
"""Create instances of SingletonWithNewArgs."""
print(SingletonWithNewArgs(10))
print(SingletonWithNewArgs())
print(SingletonWithNewArgs(20))
Das Borg-Pattern
Alex Martelli argumentiert [2], dass nicht die Identität der Instanzen die gewünschte Singleton-Funktionalität am besten umsetzt, sondern der Shared State zwischen Instanzen: Die Instanzen sind unterschiedliche Objekte. verhalten sich aber vollständig identisch. In Anlehnung an die Mensch-Maschine-Hybriden aus Star Trek, die alle miteinander und mit der Königin alle Gedanken teilen, hat er für das Muster den Namen Borg gewählt.
Listing 8 zeigt eine Implementierung. Wichtig ist die Zuweisung von »_shared_state« zu »__dict__« aller Instanzen. Eine Zuweisung erzeugt links vom Gleichheitszeichen eine neue Referenz auf das Objekt rechts vom Gleichheitszeichen. Damit teilen sich alle Instanzen »__dict__«, das alle Instanzattribute enthält. Die Funktionalität in Listing 8 entspricht bis auf die verschiedenen Objekt-IDs der in Listing 2.
Für das Borg-Pattern kann auch eine Metaklasse zum Einsatz kommen. Der Code in Listing 6 bildet dazu eine gute Grundlage und bedarf nur einiger weniger Anpassungen.
Listing 8
Borg
class Borg:
"""All instances share state.
"""
_shared_state = {}
def __new__(cls, *args, **kwargs):
instance = super().__new__(cls)
instance.__dict__ = cls._shared_state
return instance
class AllTheSame(Borg):
"""Test borg.
"""
def __init__(self, value=None):
self.value = value
def __repr__(self):
return f'{id(self)} {self.__class__.__name__}(value={self.value!r})'
def create_borgs():
"""Make instance.
"""
borg1 = AllTheSame(10)
print('borg1:', borg1)
borg2 = AllTheSame(20)
print('borg2:', borg2)
print('borg1:', borg1)
### Aufruf ###
create_borgs()
borg1: 6008347600 AllTheSame(value=10)
borg2: 6008346320 AllTheSame(value=20)
borg1: 6008347600 AllTheSame(value=20)
Fazit
Python bietet mehrere Möglichkeiten, das Singleton-Pattern umzusetzen. Je nach Anspruch und Anwendungsfall lässt es sich mit wenig Aufwand als ein Python-Modul oder stattdessen als Metaklasse mit vollständiger Kontrolle über das Klassenverhalten implementieren.
Python ist äußerst dynamisch und erlaubt tiefgehende Eingriffe in das Programmverhalten zur Laufzeit, die bei anderen Sprachen eher zur Compile-Zeit erfolgen. Zudem eignet sich die Sprache für Anwender mit unterschiedlichen Programmiererfahrungen: Experten können komplexe Implementierungen mit Metaklassen umsetzen, die dann Programmierer mit grundlegenden Kenntnissen der objektorientierten Programmierung anwenden können. (jcb/jlu)
Der Autor
Dr.-Ing. Mike Müller, Geschäftsführer der Python Academy [3] und erfahrener Python-Trainer, nutzt Python als bevorzugte Programmiersprache, seit er es 1999 entdeckte. Er ist erster Vorstandsvorsitzender des Python Software Verband e.V. [4] und war Chairman der EuroPython 2014 in Berlin. Auch die EuroSciPy 2008 und 2009 sowie die PyCon DE 2011 und 2012 hat er erfolgreich geleitet.
Infos
- “Design Patterns” :https://de.wikipedia.org/wiki/Entwurfsmuster_(Buch)
- Martelli zum Borg-Pattern: https://code.activestate.com/recipes/66531-singleton-we-dont-need-no-stinkin-singleton-the-bo
- Python Academy: https://python-academy.de
- Python Software Verband e.V.: https://python-verband.org





