Metaklassen


Definition Metaklasse

Metaklassen: Kleinsche Flasche Eine Metaklasse ist eine Klasse deren Instanzen Klassen sind. Wie eine "gewöhnliche" Klasse das Verhalten ihrer Instanzen definiert, so definiert auch eine Metaklasse das Verhalten ihrer Instanzen, aber die Instanzen sind in diesem Fall Klassen.

Metaklassen werden nicht von allen objektorientierten Programmiersprachen unterstützt. Die Programmiersprachen, die sie implementiert haben, unterscheiden sich jedoch erheblich in der Art, wie die Metaklassen implementiert sind. Python unterstützt Metaklassen.
Manche Programmierer sehen Metaklassen in Python als "Lösungen, die auf eine Problem warten".
Dennoch gibt es zahlreiche mögliche Anwendungsgebiete für Metaklassen. Um nur einige zu nennen:



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()
Die Ausgabe bestätigt das bisher Geschriebene:
clsname:  A
superclasses:  (<class '__main__.S'>,)
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