Python: Was sind Metaklassen?
Eine Metaklasse ist die Klasse einer Klasse. Der Satz stimmt und hilft alleine kein bisschen weiter. Deshalb hier der Weg dorthin, wann welcher Hook tatsächlich läuft — und warum du vermutlich etwas Einfacheres willst.
Klassen sind Objekte
Das ist das Stück, das man zuerst braucht. Eine Klasse ist in Python keine Deklaration zur Übersetzungszeit, sondern ein Objekt, das zur Laufzeit entsteht und das du herumreichen kannst:
type(42) : int
type(Plain) : type <- the class itself has a type
type(type) : type <- type is its own type
isinstance(Plain, object) : True
42 ist eine Instanz von int. Plain ist eine Instanz von type. Und
type ist eine Instanz von sich selbst — dort hört die Rekursion auf.
type() mit drei Argumenten baut eine Klasse
type() mit einem
Argument kennst du. Mit dreien erzeugt es eine Klasse:
Dynamic = type("Dynamic", (), {"greet": lambda self: "hallo", "version": 1})
type('Dynamic', (), {...}) -> <class '__main__.Dynamic'>
d.greet() : hello
d.version : 1
Name, Basisklassen, Namensraum. Das ist kein Trick, sondern genau das, was die
class-Anweisung tut: Python sammelt den Rumpf in ein Dictionary und ruft
type auf.
Eine Metaklasse ersetzt diesen Aufruf
Wenn type normalerweise deine Klasse baut, dann ist eine Metaklasse das, was
du an dessen Stelle setzt:
class Meta(type):
def __new__(mcls, name, bases, namespace, **kwargs):
namespace["added_by_meta"] = True
return super().__new__(mcls, name, bases, namespace)
class Made(metaclass=Meta):
x = 1
Meta.__new__ running for 'Made'
bases : ()
namespace : ['method', 'x']
type(Made) : Meta
Made.added_by_meta : True <- the metaclass put it there
Die Metaklasse hat beim Bau der Klasse zugesehen, den Namensraum verändert, und das Attribut steht an der fertigen Klasse.
Wann die Hooks laufen
Das ist der Teil, den man festnageln sollte, denn ein falsches Bild davon ist die Quelle der meisten Verwirrung:
1. __prepare__ (returns the namespace mapping)
2. __new__ (creates the class object)
3. __init__ (class object already exists)
now instantiating:
4. __call__ (only when an INSTANCE is made)
Die ersten drei laufen einmal, bei der Definition der Klasse. Nicht pro
Instanz. Zwei Instanzen von Made zu erzeugen gibt überhaupt keine weitere
Ausgabe:
creating two instances now -- note that Meta.__new__ does NOT run again:
(nothing printed)
__call__ fällt aus der Reihe — es läuft, wenn jemand Made() schreibt, und
damit steuerst du die Instanziierung:
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
Config() is Config() : True
Metaklassen werden vererbt
type(Child) : Meta <- inherited from Made
Unterklassen bekommen die Metaklasse mit, und __new__ läuft für jede von
ihnen erneut — diesmal mit nicht-leerem bases. Über genau diesen Unterschied
überspringt das Registry-Muster weiter unten seine eigene Basisklasse.
Wofür man sie tatsächlich verwendet
Die ehrliche Antwort lautet: für ein Register, für ein ORM oder für ein Framework, das von jeder Unterklasse wissen muss, die jemand schreibt.
class PluginMeta(type):
registry = {}
def __init__(cls, name, bases, namespace):
super().__init__(name, bases, namespace)
if bases: # die Basisklasse selbst ueberspringen
PluginMeta.registry[name.lower()] = cls
registry after defining two subclasses : ['csvexporter', 'jsonexporter']
-> no explicit registration call anywhere
Eine Unterklasse zu definieren genügt. Niemand muss an einen Dekorator oder an einen Registrierungsaufruf denken — und genau deshalb funktionieren Django-Models und SQLAlchemy so, wie sie funktionieren.
Aber du willst vermutlich init_subclass
Seit Python 3.6 gibt es einen Hook, der dieselbe Aufgabe ohne Metaklasse erledigt, eingeführt mit PEP 487:
class Basis:
registry = {}
def __init_subclass__(cls, /, label=None, **kwargs):
super().__init_subclass__(**kwargs)
Basis.registry[label or cls.__name__.lower()] = cls
class Alpha(Basis, label="a"):
pass
registry : ['a', 'beta']
Dasselbe Ergebnis, eine ganz gewöhnliche Klasse, und es nimmt sogar
Schlüsselwortargumente aus der Klassendefinition entgegen. Für den Fall mit
Deskriptoren gibt es außerdem
__set_name__.
Zusammen decken die beiden das meiste ab, wofür man sonst zur Metaklasse greift — und sie vermeiden das folgende Problem.
Der Grund, sie in Bibliotheken zu meiden
Jede Klasse hat genau eine Metaklasse. Erbst du von zwei Klassen mit verschiedenen, kommt Python nicht weiter:
class UsesA(metaclass=MetaA): pass
class UsesB(metaclass=MetaB): pass
class Both(UsesA, UsesB): pass
TypeError: metaclass conflict: the metaclass of a derived class must be a
(non-strict) subclass of the metaclasses of all its bases
Der einzige Ausweg ist eine dritte Metaklasse, die von beiden erbt — und die kannst du nur schreiben, wenn dir beide Bibliotheken gehören. Wer eine Metaklasse in einem öffentlichen Paket ausliefert, hat die Vererbungsentscheidungen seiner Nutzer bereits getroffen.
Du benutzt sie längst
Drei, die dir sicher schon begegnet sind:
type(abc.ABC) : ABCMeta
type(an Enum subclass) : EnumType
type(int) : type
abc macht über ABCMeta das
Instanziieren einer abstrakten Klasse zum Fehler, und
enum regelt über EnumType
die Suche nach Mitgliedern und die Reihenfolge beim Iterieren. Beide sind gute
Beispiele dafür, wann sich der Aufwand lohnt: Verhalten auf Framework-Ebene,
das man sonst in jeder Unterklasse von Hand wiederholen müsste.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).