Metaklassen

Metaklassen: Kleinsche Flasche Dieses Kapitel befindet sich noch in Entwicklung! Das bedeutet, dass es noch einige Fehler und Inkonsistenten gibt!
Eine Metaklasse ist eine Klasse dessen Instanzen Klassen sind. Wie eine "gewöhnliche" Klasse das Verhalten dessen Instanzen definiert, so definieren Metaklassen das Verhalten von Klassen und dessen Instanzen.
Manche Programmierer sehen Metaklassen in Python wie "eine Lösung, die auf ein Problem wartet".
Es gibt zahlreiche Anwendungsfälle für Metaklassen. Um nur ein paar zu nennen: - Logging und Profiling - Schnittstellen-Prüfung - Registrierungs-Klassen - Automatisches hinzufügen neuer Methoden - Automatische Property-Erstellung - Proxies - Automatische Ressourcen-Sperrung/Synchronisation

Metaklassen definieren

Prinzipiell werden Metaklassen genau so definiert, wie jede andere Klasse in Python. Jedoch erben Metaklassen von "type". Ein weiterer Unterschied ist, dass eine Metaklasse automatisch aufgerufen wird, wenn das Klassen-Statement eine Metaklassen-Endung bekommt. Mit anderen Worten: Wenn kein "metaclass" Schlüsselwort im Klassen-Header benutzt wird, so wird type() aufgerufen. Wenn "metaclass" im Klassen-Header steht, wird die zugewiesene Klasse aufgerufen, statt type().

Wir erstellen eine einfache Metaklasse. Sie ist nutzlos, ausser dass sie den Inhalt der Argumente in der __new__ Methode ausgibt und die Ergebnisse von type.__new__ zurückzugeben:
class LittleMeta(type):
    def __new__(cls, clsname, superclasses, attributedict):
        print("clsname: ", clsname)
        print("superclasses: ", superclasses)
        print("attributedict: ", attributedict)
        return type.__new__(cls, clsname, superclasses, attributedict)
Im folgenden Beispiel benutzen wir die Metaklasse "LittleMeta":
class S:
    pass
    
class A(S, metaclass=LittleMeta):
    pass
    
a = A()

clsname:  A
superclasses:  (,)
attributedict:  {'__module__': '__main__', '__qualname__': 'A'}
Wir sehen, dass LittleMeta.__new__ aufgerufen wird und nicht type.__new__.
Kommen wir auf das letzt Kapitel zurück: Wir definieren eine Metaklasse "EssentialAnswers" die fähig ist, die Methode augment_answer automatisch anzufügen:
x = input("Do you need the answer? (y/n): ")
if x:
    required = True
else:
    required = False
    
def the_answer(self, *args):              
        return 42
        
class EssentialAnswers(type):
    def __init__(cls, clsname, superclasses, attributedict):
        if required:
            cls.the_answer = the_answer
                               
class Philosopher1(metaclass=EssentialAnswers): 
    pass
class Philosopher2(metaclass=EssentialAnswers): 
    pass
class Philosopher3(metaclass=EssentialAnswers): 
    pass
    

plato = Philosopher1()
print(plato.the_answer())

kant = Philosopher2()
# let's see what Kant has to say :-)
print(kant.the_answer())


Do you need the answer? (y/n): y
42
42
Wir haben im Kapitel "Beziehungen zwischen Klasse und Typ" gelernt, dass Python nach der Klassen-Definition folgenden Aufruf macht:
type(classname, superclasses, attributes_dict)
Das passiert nicht, wenn eine Metaklasse im Header definiert wurde. Das haben wir im vorigen Beispiel gemacht. Unsere Klassen Philosopher1, Philosopher2 und Philosopher3 wurden mit der Metaklasse EssentialAnswers gekoppelt. Darum wird EssentialAnswer ausgeführt statt type.
EssentialAnswer(classname, superclasses, attributes_dict)
Genauer gesagt werden die Argumente des Aufrufs wie folgt gesetzt:
EssentialAnswer('Philopsopher1', 
                (), 
                {'__module__': '__main__', '__qualname__': 'Philosopher1'})
Analog dazu natürlich auch die anderen Philosopher-Klassen.

Singletons mit Metaklassen erstellen

Das Singleton-Muster ist ein Entwurfs-Muster welches die Erstellung von Instanzen auf eine beschränkt. Es wird dann genutzt, wenn nur ein Objekt gebraucht wird. Das Konzept kann, etwas verallgemeinert, auf die Instanziierung auf eine bestimmte Anzahl von Objekten begrenzt werden. In der Mathematik werden Singletons benutzt für Mengen mit exakt einem Element.
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 SingletonClass(metaclass=Singleton):
    pass
class RegularClass():
    pass
    
    
x = SingletonClass()
y = SingletonClass()

print(x == y)

x = RegularClass()
y = RegularClass()

print(x == y)


True
False