Singletons
Eine Sache, die man als Programmierer objektorientierter Sprachen immer wieder braucht, sind Singleton-Objekte. Singletons sind Objekte, die die einzigen Instanzen ihrer spezifischen Klassen sind, es gibt alle möglichen Gründe, warum man so etwas manchmal haben möchte, ein Beispiel aus einer Programmieren-Übung für Java war ein Primzahlen-Generator mittels des Siebs des Erastothenes. Da man die bereits berechneten Primzahlen nicht jedes Mal wieder neu berechnen will, reicht es, wenn man immer wieder das gleiche Objekt zurück liefert, sofern man sicher stellt, dass die Liste der Primzahlen nicht von außen modifiziert werden kann.
In Python spielen zwei Methoden beim Instanziieren einer Klasse eine Rolle. Die Klasse hier soll A heißen, x und y seien beliebige lokale Variablen:
>>> a = A(x, y)
Bei diesem Aufruf wird zunächst die Methode __new__ als Klassenmethode von A aufgerufen (d.h. hier mit den Argumenten A, x und y). Ist der Rückgabewert von __new__ eine Instanz von A (auch Instanzen von Subklassen zählen), wird auf dieser neuen Instanz die Methode __init__ aufgerufen, mit den gleichen Argumenten, wie sie beim Aufruf der Klasse übergeben wurden (und natürlich dem explizitem self).
Eine Möglichkeit also, Singletons zu implementieren, wäre einfach __new__ so zu verändern, dass es immer das gleiche Objekt zurückliefert:
class Singleton(object):
__instance = None
def __new__(cls, *args, **kwds):
if cls.__instance is None:
cls.__instance = super(Singleton, cls).__new__(cls, *args, **kwds)
return cls.__instance
Diese Implementation hat mehrere Schwächen. Die meiner Meinung nach größte ist, dass immer wieder __init__ für das Singleton-Objekt aufgerufen wird, da dies ja das einzige Kriterium dafür erfüllt, nämlich Instanz von cls zu sein. (Vorausgesetzt der super-Aufruf liefert ein entsprechendes Objekt.) Dies ist z.B. ein Problem für den Primzahl-Generator, der z.B. in __init__ eine leere Liste erzeugt, in der die berechneten Zahlen gespeichert werden sollen. Die anderen Schwächen, wie z.B., dass in einer Subklasse eine schon existierende Instanz der Superklasse zurückgeliefert wird, ließen sich durch ein bisschen Aufwand leicht beheben.
Mein Ansatz ist aber ein anderer und involviert - wer hätte es gedacht? - Metaklassen. Konstruktoraufrufe in Python sehen nämlich nicht nur wie Funktionsaufrufe aus, sie verhalten sich auch genau so. Für jedes x als Instanz von Y wird der Ausdruck x(...) (dabei steht ... für beliebige Argumente) in den folgenden übersetzt: Y.__call__(x, ...). Für Funktionen führt __call__ in der Funktionen-Klasse function (über den __builtin__-Namespace leider nicht erreichbar, wohl aber als types.FunctionType), eben die Funktion aus und für Klassen (Instanzen von type) implementiert __call__ eben genau das oben beschriebene Verhalten. In Python formuliert, liest sich das also folgendermaßen:
# in der Klassendefinition von type
def __call__(self, *args, **kwds):
new_object = self.__new__(self, *args, **kwds)
if isinstance(new_object, self):
new_object.__init__(*args, **kwds)
return new_object
Zu beachten ist hier, dass self das Klassenobjekt ist. Der besondere Aufruf von __new__ (mit explitem self) kommt dadurch zustande, dass __new__ bei der Klassendefinion eine Sonderbehandlung erfährt und immer in eine statische Methode umgewandelt wird, damit man dies nicht explizit machen muss. Eine Idee also, um Singletons zu implementieren ist die Verwendung einer Singleton-Metaklasse, die __call__ überschreibt:
class SingletonClass(type):
singletons = {}
def __call__(self, *args, **kwds):
try:
return self.singletons[self]
except KeyError:
self.singletons[self] = super(SingletonClass, self).__call__(*args, **kwds)
return self.singletons[self]
class Singleton(object):
__metaclass__ = SingletonClass
Eine Klasse die nun von Singleton erbt, hat automatisch Singleton-Charakteristik:
>>> class A(Singleton):
... pass
>>> A() is A()
True
>>> class B(A):
... pass
>>> B()
<__main__.B object at 0xb7c01a8c>
>>> Singleton.singletons
{<class '__main__.B'>: <__main__.B object at 0xb7c01a8c>,
<class '__main__.A'>: <__main__.A object at 0xb7c01a4c>}