Eine Metaklasse ist eine Klasse, deren Instanzen Klassen sind. Wie eine "normale" Klasse das Verhalten der Instanzen der Klasse definiert, definiert eine Metaklasse das Verhalten von Klassen und ihren Instanzen.
Metaklassen werden nicht von jeder objektorientierten Programmiersprache unterstützt. Die Programmiersprachen, die Metaklassen unterstützen, unterscheiden sich erheblich in der Art und Weise, wie sie implementiert werden. Python unterstützt Metaklassen.
Einige Programmierer sehen Metaklassen in Python als "Lösungen, die auf ein Problem warten oder suchen".
Es gibt zahlreiche Anwendungsfälle für Metaklassen. Nur um ein paar zu nennen:
- Protokollierung (logging) und Profilerstellung
- Konsistenzprüfung von Schnittstellen
- Registrieren von Klassen zur Erstellungszeit
- automatisches Hinzufügen neuer Methoden
- automatische Erstellung von Properties
- Proxies
- automatische Ressourcensperre und Synchronisation.
Grundsätzlich werden Metaklassen wie jede andere Python-Klasse definiert, aber sie sind Klassen, die von type
erben. Ein weiterer Unterschied besteht darin, dass eine Metaklasse automatisch aufgerufen wird, wenn die Klassenanweisung mit einer Metaklasse endet. Mit anderen Worten: Wenn nach den Basisklassen (möglicherweise gibt es auch keine Basisklassen) des Klassenkopfs kein Schlüsselwort "metaclass" übergeben wird, wird type()
(d. h. __call__
von der type
-Klasse) aufgerufen. Wenn andererseits ein Metaklassen-Schlüsselwort verwendet wird, wird die ihm zugewiesene Klasse anstelle des Typs aufgerufen.
Jetzt erstellen wir eine sehr einfache Metaklasse. Sie ist für nichts nützlich, außer dass sie den Inhalt ihrer Argumente in der Methode __new__
ausgibt und die Ergebnisse des Aufrufs type.__new__
zurückliefert:
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)
Wir werden die Metaklasse "LittleMeta" im folgenden Beispiel verwenden:
class S:
pass
class A(S, metaclass=LittleMeta):
pass
a = A()
Wir können sehen, dass LittleMeta.__ new__
aufgerufen wurde und nicht type.__ new__
.
Wiederaufnahme unseres Threads aus dem letzten Kapitel: Wir definieren eine Metaklasse EssentialAnswers
, die unsere augment_answer
-Methode automatisch einbinden kann:
x = input("Benötigst du die Antwort=? (j/n): ")
if x.lower() == "j":
benötigt = True
else:
benötigt = False
def der_antwort(self, *args):
return 42
class EssentialAnswers(type):
def __init__(cls, clsname, superclasses, attributedict):
if benötigt:
cls.der_antwort = der_antwort
class Philosoph1(metaclass=EssentialAnswers):
pass
class Philosoph2(metaclass=EssentialAnswers):
pass
class Philosoph3(metaclass=EssentialAnswers):
pass
plato = Philosoph1()
print(plato.der_antwort())
kant = Philosoph2()
# lass uns sehen was Kant zu sagen hat :-)
print(kant.der_antwort())
In unserem Kapitel "Typ- und Klassenbeziehung" haben wir erfahren, dass Python nach der Verarbeitung der Klassendefinition aufruft
type(classname, superclasses, attributes_dict)
Dies ist nicht der Fall, wenn im Header eine Metaklasse deklariert wurde. Das haben wir in unserem vorherigen Beispiel getan. Unsere Klassen Philosoph1, Philosoph2 und Philosoph3 wurden mit der Metaklasse EssentialAnswers verknüpft. Aus diesem Grund wird EssentialAnswer anstelle von type aufgerufen:
EssentialAnswer(classname, superclasses, attributes_dict)
Um genau zu sein, werden für die Argumente der Aufrufe die folgenden Werte festgelegt:
EssentialAnswer('Philopsopher1', (), {'__module__': '__main__', '__qualname__': 'Philosopher1'})
Die anderen Philosophenklassen werden analog behandelt.
Das Singleton-Muster ist ein Entwurfsmuster, das die Instanziierung einer Klasse auf ein Objekt beschränkt. Es wird in Fällen verwendet, in denen genau ein Objekt benötigt wird. Das Konzept kann verallgemeinert werden, um die Instanziierung auf eine bestimmte oder feste Anzahl von Objekten zu beschränken. Der Begriff stammt aus der Mathematik, wo ein Singleton - auch als Einheitensatz bezeichnet - für Mengen mit genau einem Element verwendet wird.
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)
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = object.__new__(cls, *args, **kwargs)
return cls._instance
class SingletonClass(Singleton):
pass
class RegularClass():
pass
x = SingletonClass()
y = SingletonClass()
print(x == y)
x = RegularClass()
y = RegularClass()
print(x == y)