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, ...).