Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 3115 – Metaklassen in Python 3000

Autor:
Talin <viridia at gmail.com>
Status:
Final
Typ:
Standards Track
Erstellt:
07. Mrz. 2007
Python-Version:
3.0
Post-History:
11. Mrz. 2007, 14. Mrz. 2007

Inhaltsverzeichnis

Zusammenfassung

Diese PEP schlägt eine Änderung der Syntax für die Deklaration von Metaklassen vor und ändert die Semantik für die Konstruktion von Klassen mit Metaklassen.

Begründung

Für diese PEP gibt es zwei Begründungen, die beide etwas subtil sind.

Der Hauptgrund für die Änderung der Funktionsweise von Metaklassen ist, dass es eine Reihe interessanter Anwendungsfälle gibt, die erfordern, dass die Metaklasse früher in den Prozess der Klassenerstellung eingreift, als es derzeit möglich ist. Derzeit ist der Metaklassenmechanismus im Wesentlichen ein Nachbearbeitungsschritt. Mit dem Aufkommen von Klassen-Decorators können viele dieser Nachbearbeitungsaufgaben vom Decorator-Mechanismus übernommen werden.

Insbesondere gibt es einen wichtigen Bereich von Anwendungsfällen, in denen es nützlich wäre, die Reihenfolge beizubehalten, in der Klassenmitglieder deklariert werden. Normale Python-Objekte speichern ihre Mitglieder in einem Wörterbuch, in dem die Reihenfolge unwichtig ist und Mitglieder streng nach Namen abgerufen werden. Python wird jedoch oft verwendet, um mit externen Systemen zu interagieren, in denen die Mitglieder nach einer impliziten Reihenfolge organisiert sind. Beispiele hierfür sind die Deklaration von C-Strukturen; COM-Objekte; automatische Übersetzung von Python-Klassen in IDL- oder Datenbankschemata, wie sie in einem ORM verwendet werden; und so weiter.

In solchen Fällen wäre es für einen Python-Programmierer nützlich, solche Reihenfolgen direkt mithilfe der Deklarationsreihenfolge von Klassenmitgliedern anzugeben. Derzeit müssen solche Reihenfolgen explizit mithilfe eines anderen Mechanismus angegeben werden (siehe das ctypes-Modul als Beispiel).

Leider erlaubt die aktuelle Methode zur Deklaration einer Metaklasse dies nicht, da die Reihenfolgeinformationen bereits verloren gegangen sind, wenn die Metaklasse ins Spiel kommt. Indem die Metaklasse früher in den Klassenerstellungsprozess einbezogen wird, ermöglicht das neue System die Beibehaltung und Untersuchung der Reihenfolge oder anderer früher Artefakte der Erstellung.

Der vorgeschlagene Metaklassenmechanismus unterstützt auch eine Reihe anderer interessanter Anwendungsfälle über die Beibehaltung der Reihenfolge von Deklarationen hinaus. Ein Anwendungsfall ist das Einfügen von Symbolen in den Namensraum des Klassenkörpers, die nur während der Klassenerstellung gültig sind. Ein Beispiel hierfür könnten „Feldkonstruktoren“ sein, kleine Funktionen, die bei der Erstellung von Klassenmitgliedern verwendet werden. Eine weitere interessante Möglichkeit ist die Unterstützung von Vorwärtsreferenzen, d. h. Verweise auf Python-Symbole, die weiter unten im Klassenkörper deklariert sind.

Die andere, schwächere Begründung ist rein kosmetisch: Die aktuelle Methode zur Angabe einer Metaklasse ist die Zuweisung an die spezielle Variable __metaclass__, die von einigen als ästhetisch weniger als ideal angesehen wird. Andere widersprechen dieser Meinung vehement. Diese PEP befasst sich nicht mit diesem Problem, außer es zu erwähnen, da ästhetische Debatten nicht durch logische Beweise gelöst werden können.

Spezifikation

Im neuen Modell ist die Syntax für die Angabe einer Metaklasse ein Schlüsselwortargument in der Liste der Basisklassen

class Foo(base1, base2, metaclass=mymeta):
    ...

Zusätzliche Schlüsselwörter werden hier ebenfalls erlaubt sein und werden an die Metaklasse übergeben, wie im folgenden Beispiel

class Foo(base1, base2, metaclass=mymeta, private=True):
    ...

Beachten Sie, dass diese PEP keinen Versuch unternimmt, zu definieren, was diese anderen Schlüsselwörter sein könnten – das liegt an den Implementierern der Metaklasse, dies zu bestimmen.

Allgemeiner unterstützt die Parameterliste, die an eine Klassendefinition übergeben wird, nun alle Funktionen eines Funktionsaufrufs, was bedeutet, dass Sie jetzt *args und **kwargs-Stil-Argumente in der Liste der Klassenbasen verwenden können

class Foo(*bases, **kwds):
    ...

Aufruf der Metaklasse

Im aktuellen Metaklassensystem kann das Metaklassenobjekt jeder aufrufbare Typ sein. Dies ändert sich nicht, aber um alle neuen Funktionen voll auszuschöpfen, muss die Metaklasse ein zusätzliches Attribut haben, das während der Vor-Klassenerstellung verwendet wird.

Dieses Attribut heißt __prepare__ und wird als Funktion vor der Auswertung des Klassenkörpers aufgerufen. Die Funktion __prepare__ nimmt zwei Positionsargumente und eine beliebige Anzahl von Schlüsselwortargumenten entgegen. Die beiden Positionsargumente sind

name der Name der erstellten Klasse.
basisklassen die Liste der Basisklassen.

Der Interpreter prüft immer die Existenz von __prepare__, bevor er sie aufruft; Wenn sie nicht vorhanden ist, wird ein reguläres Wörterbuch verwendet, wie im folgenden Python-Snippet gezeigt.

def prepare_class(name, *bases, metaclass=None, **kwargs):
    if metaclass is None:
        metaclass = compute_default_metaclass(bases)
    prepare = getattr(metaclass, '__prepare__', None)
    if prepare is not None:
        return prepare(name, bases, **kwargs)
    else:
        return dict()

Das obige Beispiel veranschaulicht, wie die Argumente für „class“ interpretiert werden. Der Klassenname ist das erste Argument, gefolgt von einer beliebigen Anzahl von Basisklassen. Nach den Basisklassen können ein oder mehrere Schlüsselwortargumente folgen, von denen eines *metaclass* sein kann. Beachten Sie, dass das Argument *metaclass* nicht in *kwargs* enthalten ist, da es vom normalen Parameterzuordnungsalgorithmus herausgefiltert wird. (Beachten Sie auch, dass *metaclass* ein schlüsselwort-only-Argument gemäß PEP 3102 ist.)

Obwohl __prepare__ nicht erforderlich ist, implementiert die Standard-Metaklasse („type“) sie zur Bequemlichkeit von Unterklassen, die sie über super() aufrufen.

__prepare__ gibt ein wörterbuchähnliches Objekt zurück, das verwendet wird, um die Klassendefinitionen während der Auswertung des Klassenkörpers zu speichern. Mit anderen Worten, der Klassenkörper wird als Funktionsblock ausgewertet (genau wie jetzt), außer dass das Wörterbuch der lokalen Variablen durch das von __prepare__ zurückgegebene Wörterbuch ersetzt wird. Dieses Wörterbuchobjekt kann ein reguläres Wörterbuch oder ein benutzerdefinierter Abbildungstyp sein.

Dieses wörterbuchähnliche Objekt muss nicht die vollständige Wörterbuchschnittstelle unterstützen. Ein Wörterbuch, das einen begrenzten Satz von Wörterbuchoperationen unterstützt, schränkt ein, welche Aktionen während der Auswertung des Klassenkörpers auftreten können. Eine minimale Implementierung könnte nur das Hinzufügen und Abrufen von Werten aus dem Wörterbuch unterstützen – die meisten Klassenkörper werden während der Auswertung nicht mehr tun. Für einige Klassen kann es wünschenswert sein, auch das Löschen zu unterstützen. Viele Metaklassen müssen anschließend eine Kopie dieses Wörterbuchs erstellen, sodass Iteration oder andere Mittel zum Auslesen des Wörterbuchinhalts ebenfalls nützlich sein können.

Die Methode __prepare__ wird am häufigsten als Klassenmethode und nicht als Instanzmethode implementiert, da sie vor der Erstellung der Metaklasseninstanz (d. h. der Klasse selbst) aufgerufen wird.

Sobald der Klassenkörper die Auswertung abgeschlossen hat, wird die Metaklasse (als aufrufbare Funktion) mit dem Klassenwörterbuch aufgerufen, was sich nicht vom aktuellen Metaklassenmechanismus unterscheidet.

Typischerweise erstellt eine Metaklasse ein benutzerdefiniertes Wörterbuch – entweder eine Unterklasse von dict oder einen Wrapper darum –, das zusätzliche Eigenschaften enthält, die entweder vor oder während der Auswertung des Klassenkörpers gesetzt werden. Dann kann die Metaklasse in der zweiten Phase diese zusätzlichen Eigenschaften verwenden, um die Klasse weiter anzupassen.

Ein Beispiel wäre eine Metaklasse, die Informationen über die Reihenfolge von Mitgliederdeklarationen zur Erstellung einer C-Struktur verwendet. Die Metaklasse würde ein benutzerdefiniertes Wörterbuch bereitstellen, das einfach die Reihenfolge der Einfügungen aufzeichnet. Dies muss keine vollständige „ordered dict“-Implementierung sein, sondern nur eine Python-Liste von (Schlüssel, Wert)-Paaren, an die bei jeder Einfügung angehängt wird.

Beachten Sie, dass in einem solchen Fall die Metaklasse gezwungen wäre, die Möglichkeit doppelter Schlüssel zu berücksichtigen, aber in den meisten Fällen ist dies trivial. Die Metaklasse kann die erste Deklaration, die letzte, eine Kombination davon oder einfach eine Ausnahme auslösen. Es liegt an der Metaklasse zu entscheiden, wie sie diesen Fall handhaben möchte.

Beispiel

Hier ist ein einfaches Beispiel für eine Metaklasse, die eine Liste der Namen aller Klassenmitglieder in der Reihenfolge erstellt, in der sie deklariert wurden

# The custom dictionary
class member_table(dict):
    def __init__(self):
        self.member_names = []

    def __setitem__(self, key, value):
        # if the key is not already defined, add to the
        # list of keys.
        if key not in self:
            self.member_names.append(key)

        # Call superclass
        dict.__setitem__(self, key, value)

# The metaclass
class OrderedClass(type):

    # The prepare function
    @classmethod
    def __prepare__(metacls, name, bases): # No keywords in this case
        return member_table()

    # The metaclass invocation
    def __new__(cls, name, bases, classdict):
        # Note that we replace the classdict with a regular
        # dict before passing it to the superclass, so that we
        # don't continue to record member names after the class
        # has been created.
        result = type.__new__(cls, name, bases, dict(classdict))
        result.member_names = classdict.member_names
        return result

class MyClass(metaclass=OrderedClass):
    # method1 goes in array element 0
    def method1(self):
        pass

    # method2 goes in array element 1
    def method2(self):
        pass

Beispielimplementierung

Guido van Rossum hat einen Patch erstellt, der die neue Funktionalität implementiert: https://bugs.python.org/issue1681101

Alternative Vorschläge

Josiah Carlson schlug vor, den Namen ‚type‘ anstelle von ‚metaclass‘ zu verwenden, nach der Theorie, dass das, was wirklich angegeben wird, der Typ des Typs ist. Das ist zwar technisch korrekt, aber aus der Sicht eines Programmierers, der eine neue Klasse erstellt, verwirrend. Aus der Sicht des Anwendungsentwicklers ist der ‚Typ‘, an dem er interessiert ist, die Klasse, die er schreibt; der Typ dieses Typs ist die Metaklasse.

In der Diskussion gab es einige Einwände gegen den „zwei-Phasen“-Erstellungsprozess, bei dem die Metaklasse zweimal aufgerufen wird, einmal zur Erstellung des Klassenwörterbuchs und einmal zur „Fertigstellung“ der Klasse. Einige Leute waren der Meinung, dass diese beiden Phasen vollständig getrennt sein sollten, da es separate Syntax für die Angabe des benutzerdefinierten Dict und für die Angabe der Metaklasse geben sollte. In den meisten Fällen werden die beiden jedoch eng miteinander verbunden sein und die Metaklasse wird wahrscheinlich intime Kenntnisse der internen Details des Klassen-Dict haben. Die Anforderung, dass der Programmierer sicherstellt, dass der richtige Dict-Typ und der richtige Metaklassentyp zusammen verwendet werden, schafft eine zusätzliche und unnötige Belastung für den Programmierer.

Ein weiterer guter Vorschlag war, einfach für alle Klassen ein geordnetes Dict zu verwenden und den gesamten Mechanismus für benutzerdefinierte Dicts zu überspringen. Dies basierte auf der Beobachtung, dass die meisten Anwendungsfälle für ein benutzerdefiniertes Dict dazu dienten, Reihenfolgeinformationen zu bewahren. Diese Idee hat jedoch mehrere Nachteile: Erstens muss eine Implementierung eines geordneten Dicts zur Menge der eingebauten Typen in Python hinzugefügt werden, und zweitens würde sie für alle Klassendeklarationen einen leichten Geschwindigkeitsnachteil (und Komplexitätsnachteil) mit sich bringen. Später kamen mehrere Personen auf Ideen für Anwendungsfälle für benutzerdefinierte Wörterbücher, die über die Beibehaltung von Feldreihenfolgen hinausgehen, sodass diese Idee verworfen wurde.

Abwärtskompatibilität

Es wäre möglich, die bestehende __metaclass__-Syntax beizubehalten. Alternativ wäre es nicht allzu schwierig, die Syntaxregeln des Py3K-Übersetzungstools zu modifizieren, um von der alten zur neuen Syntax zu konvertieren.

Referenzen

[1] [Python-3000] Metaklassen in Py3K (Ursprünglicher Vorschlag) https://mail.python.org/pipermail/python-3000/2006-December/005030.html

[2] [Python-3000] Metaklassen in Py3K (Guido's vorgeschlagene Syntax) https://mail.python.org/pipermail/python-3000/2006-December/005033.html

[3] [Python-3000] Metaklassen in Py3K (Einwände gegen zwei-Phasen-Init) https://mail.python.org/pipermail/python-3000/2006-December/005108.html

[4] [Python-3000] Metaklassen in Py3K (Immer ein geordnetes Dict verwenden) https://mail.python.org/pipermail/python-3000/2006-December/005118.html


Quelle: https://github.com/python/peps/blob/main/peps/pep-3115.rst

Zuletzt geändert: 2025-02-01 08:55:40 GMT