PEP 208 – Überarbeitung des Coercion-Modells
- Autor:
- Neil Schemenauer <nas at arctrix.com>, Marc-André Lemburg <mal at lemburg.com>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 04-Dez-2000
- Python-Version:
- 2.1
- Post-History:
Inhaltsverzeichnis
Zusammenfassung
Viele Python-Typen implementieren numerische Operationen. Wenn die Argumente einer numerischen Operation unterschiedliche Typen haben, versucht der Interpreter, die Argumente in einen gemeinsamen Typ zu zwängen (coercen). Die numerische Operation wird dann unter Verwendung dieses gemeinsamen Typs ausgeführt. Dieses PEP schlägt ein neues Typ-Flag vor, das anzeigt, dass Argumente der numerischen Operationen eines Typs nicht gezwängt werden sollen. Operationen, die die bereitgestellten Typen nicht unterstützen, zeigen dies durch die Rückgabe eines neuen Singleton-Objekts an. Typen, die das Typ-Flag nicht setzen, werden auf rückwärtskompatible Weise behandelt. Das Zulassen, dass Operationen verschiedene Typen verarbeiten, ist oft einfacher, flexibler und schneller als die Durchführung der Zwängung durch den Interpreter.
Begründung
Bei der Implementierung von numerischen oder anderen verwandten Operationen ist es oft wünschenswert, nicht nur Operationen zwischen Operanden eines einzigen Typs bereitzustellen, z. B. Integer + Integer, sondern die Idee hinter der Operation auf andere Typkombinationen auszudehnen, z. B. Integer + Float.
Ein gängiger Ansatz für diese Situation mit gemischten Typen ist die Bereitstellung einer Methode zum "Anheben" der Operanden auf einen gemeinsamen Typ (Coercion) und die anschließende Verwendung der Operandenmethode dieses Typs als Ausführungsmechanismus. Doch diese Strategie hat einige Nachteile:
- der "Anhebe"-Prozess erstellt mindestens ein neues (temporäres) Operandobjekt,
- da die Coercion-Methode nicht über die nachfolgende Operation informiert wird, ist es nicht möglich, operationsspezifische Coercion von Typen zu implementieren,
- es gibt keinen eleganten Weg, Situationen zu lösen, in denen kein gemeinsamer Typ zur Verfügung steht, und
- die Coercion-Methode muss immer vor der Methode der Operation selbst aufgerufen werden.
Eine Lösung für diese Situation ist offensichtlich notwendig, da diese Nachteile die Implementierung von Typen, die diese Funktionen benötigen, sehr umständlich, wenn nicht gar unmöglich machen. Betrachten Sie als Beispiel die Typen DateTime und DateTimeDelta [1], wobei der erste absolut und der zweite relativ ist. Man kann immer einen relativen Wert zu einem absoluten addieren, was einen neuen absoluten Wert ergibt. Es gibt jedoch keinen gemeinsamen Typ, den der bestehende Coercion-Mechanismus zur Implementierung dieser Operation verwenden könnte.
Derzeit werden PyInstance-Typen vom Interpreter speziell behandelt, indem ihre numerischen Methoden Argumente unterschiedlicher Typen übergeben bekommen. Das Entfernen dieses Sonderfalls vereinfacht den Interpreter und ermöglicht es anderen Typen, numerische Methoden zu implementieren, die sich wie Instanztypen verhalten. Dies ist besonders nützlich für Erweiterungstypen wie ExtensionClass.
Spezifikation
Anstatt eine zentrale Coercion-Methode zu verwenden, wird der Prozess der Handhabung unterschiedlicher Operanden-Typen einfach der Operation überlassen. Wenn die Operation feststellt, dass sie die gegebene Kombination von Operanden-Typen nicht verarbeiten kann, kann sie als Indikator ein spezielles Singleton zurückgeben.
Beachten Sie, dass "Zahlen" (alles, was das Zahlenprotokoll oder Teile davon implementiert) in Python bereits den ersten Teil dieser Strategie verwenden - hier konzentrieren wir uns auf die C-Ebene-API.
Um eine nahezu 100%ige Abwärtskompatibilität zu gewährleisten, müssen wir sehr vorsichtig sein, damit Zahlen, die nichts über die neue Strategie wissen (alte Zahlen), genauso gut funktionieren wie diejenigen, die das neue Schema erwarten (neue Zahlen). Darüber hinaus ist binäre Kompatibilität ein Muss, was bedeutet, dass der Interpreter nur neue Operationen zugreifen und verwenden darf, wenn die Zahl die Verfügbarkeit dieser anzeigt.
Eine Zahl neuen Stils wird vom Interpreter als solche betrachtet, wenn und nur wenn sie das Typ-Flag Py_TPFLAGS_CHECKTYPES setzt. Der Hauptunterschied zwischen einer Zahl alten Stils und einer Zahl neuen Stils besteht darin, dass numerische Slot-Funktionen nicht mehr davon ausgehen können, dass ihnen Argumente desselben Typs übergeben werden. Slots neuen Stils müssen alle Argumente auf korrekten Typ prüfen und die notwendigen Konvertierungen selbst implementieren. Dies mag so aussehen, als ob dies mehr Arbeit für den Implementierer des Typs bedeutet, ist aber tatsächlich nicht schwieriger als das Schreiben der gleichen Art von Routinen für einen Coercion-Slot alten Stils.
Wenn ein Slot neuen Stils feststellt, dass er die übergebenen Argumenttyp-Kombination nicht verarbeiten kann, kann er eine neue Referenz auf das spezielle Singleton Py_NotImplemented an den Aufrufer zurückgeben. Dies veranlasst den Aufrufer, die Operation-Slots der anderen Operanden auszuprobieren, bis er einen Slot findet, der die Operation für die spezifische Typkombination implementiert. Wenn keiner der möglichen Slots erfolgreich ist, wird ein TypeError ausgelöst.
Um die Implementierung leicht verständlich zu machen (das ganze Thema ist esoterisch genug), wird eine neue Ebene in der Handhabung numerischer Operationen eingeführt. Diese Ebene kümmert sich um alle verschiedenen Fälle, die bei der Behandlung aller möglichen Kombinationen von alten und neuen Zahlen berücksichtigt werden müssen. Sie wird durch die beiden statischen Funktionen binary_op() und ternary_op() implementiert, die beide interne Funktionen sind und nur von den Funktionen in Objects/abstract.c zugänglich sind. Die numerische API (PyNumber_*) lässt sich leicht an diese neue Ebene anpassen.
Als Nebeneffekt können alle numerischen Slots auf NULL geprüft werden (dies muss ohnehin geschehen, sodass die zusätzliche Funktion keine zusätzlichen Kosten verursacht).
Das von der Ebene zur Ausführung einer binären Operation verwendete Schema ist wie folgt:
| v | w | Ergriffene Aktion |
|---|---|---|
| neu | neu | v.op(v,w), w.op(v,w) |
| neu | alt | v.op(v,w), coerce(v,w), v.op(v,w) |
| alt | neu | w.op(v,w), coerce(v,w), v.op(v,w) |
| alt | alt | coerce(v,w), v.op(v,w) |
Die angezeigte Aktionssequenz wird von links nach rechts ausgeführt, bis entweder die Operation erfolgreich ist und ein gültiges Ergebnis (!= Py_NotImplemented) zurückgegeben wird oder eine Ausnahme ausgelöst wird. Ausnahmen werden unverändert an die aufrufende Funktion zurückgegeben. Wenn ein Slot Py_NotImplemented zurückgibt, wird das nächste Element in der Sequenz ausgeführt.
Beachten Sie, dass coerce(v,w) die alten nb_coerce-Slot-Methoden über einen Aufruf von PyNumber_Coerce() verwendet.
Ternäre Operationen haben einige zusätzliche Fälle zu behandeln:
| v | w | z | Ergriffene Aktion |
|---|---|---|---|
| neu | neu | neu | v.op(v,w,z), w.op(v,w,z), z.op(v,w,z) |
| neu | alt | neu | v.op(v,w,z), z.op(v,w,z), coerce(v,w,z), v.op(v,w,z) |
| alt | neu | neu | w.op(v,w,z), z.op(v,w,z), coerce(v,w,z), v.op(v,w,z) |
| alt | alt | neu | z.op(v,w,z), coerce(v,w,z), v.op(v,w,z) |
| neu | neu | alt | v.op(v,w,z), w.op(v,w,z), coerce(v,w,z), v.op(v,w,z) |
| neu | alt | alt | v.op(v,w,z), coerce(v,w,z), v.op(v,w,z) |
| alt | neu | alt | w.op(v,w,z), coerce(v,w,z), v.op(v,w,z) |
| alt | alt | alt | coerce(v,w,z), v.op(v,w,z) |
Die gleichen Anmerkungen wie oben, außer dass coerce(v,w,z) tatsächlich Folgendes tut:
if z != Py_None:
coerce(v,w), coerce(v,z), coerce(w,z)
else:
# treat z as absent variable
coerce(v,w)
Die aktuelle Implementierung verwendet bereits dieses Schema (es gibt nur einen ternären Slot: nb_pow(a,b,c)).
Beachten Sie, dass das Zahlenprotokoll auch für einige andere verwandte Aufgaben verwendet wird, z. B. Sequenzverkettung. Diese können ebenfalls von dem neuen Mechanismus profitieren, indem sie Operationen auf der rechten Seite für Typkombinationen implementieren, die sonst fehlschlagen würden. Nehmen Sie als Beispiel die Zeichenkettenverkettung: Derzeit können Sie nur String + String machen. Mit dem neuen Mechanismus könnte ein neuer stringähnlicher Typ new_type + string und string + new_type implementieren, obwohl Zeichenketten nichts über new_type wissen.
Da Vergleiche ebenfalls auf Coercion basieren (jedes Mal, wenn Sie eine Ganzzahl mit einer Gleitkommazahl vergleichen, wird die Ganzzahl zuerst in eine Gleitkommazahl umgewandelt und dann verglichen...), wird ein neuer Slot zur Handhabung numerischer Vergleiche benötigt.
PyObject *nb_cmp(PyObject *v, PyObject *w)
Dieser Slot sollte die beiden Objekte vergleichen und ein Ganzzahl-Objekt zurückgeben, das das Ergebnis angibt. Derzeit kann diese Ergebnisganzzahl nur -1, 0, 1 sein. Wenn der Slot die Typkombination nicht verarbeiten kann, kann er eine Referenz auf Py_NotImplemented zurückgeben. [XXX Beachten Sie, dass sich dieser Slot noch in der Entwicklung befindet, da er reiche Vergleiche berücksichtigen sollte (d. h. PEP 207).
Numerische Vergleiche werden von einer neuen numerischen Protokoll-API behandelt.
PyObject *PyNumber_Compare(PyObject *v, PyObject *w)
Diese Funktion vergleicht die beiden Objekte als "Zahlen" und gibt ein Ganzzahl-Objekt zurück, das das Ergebnis angibt. Derzeit kann diese Ergebnisganzzahl nur -1, 0, 1 sein. Falls die Operation nicht von den gegebenen Objekten verarbeitet werden kann, wird ein TypeError ausgelöst.
Die API PyObject_Compare() muss entsprechend angepasst werden, um diese neue API zu nutzen.
Weitere Änderungen umfassen die Anpassung einiger integrierter Funktionen (z. B. cmp()), um ebenfalls diese API zu nutzen. Außerdem muss PyNumber_CoerceEx() auf neue Zahlen prüfen, bevor der nb_coerce-Slot aufgerufen wird. Neue Zahlen stellen keinen Coercion-Slot bereit und können daher nicht explizit gezwängt werden.
Referenzimplementierung
Ein vorläufiger Patch für die CVS-Version von Python ist über den Source Forge Patch Manager verfügbar [2].
Danksagungen
Dieses PEP und der Patch basieren stark auf der Arbeit von Marc-André Lemburg [3].
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Referenzen
Quelle: https://github.com/python/peps/blob/main/peps/pep-0208.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT