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

Python Enhancement Proposals

PEP 203 – Augmented Assignments

Autor:
Thomas Wouters <thomas at python.org>
Status:
Final
Typ:
Standards Track
Erstellt:
13-Jul-2000
Python-Version:
2.0
Post-History:
14-Aug-2000

Inhaltsverzeichnis

Einleitung

Diese PEP beschreibt den Vorschlag für Augmented Assignments für Python 2.0. Diese PEP verfolgt den Status und die Zuständigkeit für dieses Feature, das für die Einführung in Python 2.0 geplant ist. Sie enthält eine Beschreibung des Features und skizziert die notwendigen Änderungen zur Unterstützung des Features. Diese PEP fasst Diskussionen aus Mailinglisten zusammen [1] und stellt, wo angebracht, URLs für weitere Informationen bereit. Die CVS-Revisionshistorie dieser Datei enthält die endgültige historische Aufzeichnung.

Proposed Semantics

Der vorgeschlagene Patch, der Augmented Assignments zu Python hinzufügt, führt die folgenden neuen Operatoren ein

+= -= *= /= %= **= <<= >>= &= ^= |=

Sie implementieren denselben Operator wie ihre normale binäre Form, mit der Ausnahme, dass die Operation in-place durchgeführt wird, wenn das Objekt auf der linken Seite dies unterstützt, und dass die linke Seite nur einmal ausgewertet wird.

Sie verhalten sich tatsächlich wie Augmented Assignments, da sie alle normalen Lade- und Speicheroperationen zusätzlich zur binären Operation ausführen, für die sie gedacht sind. Gegeben sei also der Ausdruck

x += y

Das Objekt x wird geladen, dann wird y dazu addiert, und das resultierende Objekt wird an der ursprünglichen Stelle zurückgeschrieben. Die genaue Aktion, die auf die beiden Argumente angewendet wird, hängt vom Typ von x und möglicherweise von y ab.

Die Idee hinter Augmented Assignments in Python ist, dass es nicht nur eine einfachere Möglichkeit ist, die gängige Praxis zu schreiben, das Ergebnis einer binären Operation im linken Operanden zu speichern, sondern auch eine Möglichkeit für den fraglichen linken Operanden zu wissen, dass er auf sich selbst operieren soll, anstatt eine modifizierte Kopie von sich selbst zu erstellen.

Um dies zu ermöglichen, werden Python-Klassen und C-Erweiterungstypen eine Reihe neuer Hooks hinzugefügt, die aufgerufen werden, wenn das fragliche Objekt auf der linken Seite einer Augmented Assignment-Operation verwendet wird. Wenn die Klasse oder der Typ die in-place Hooks nicht implementiert, werden die normalen Hooks für die jeweilige binäre Operation verwendet.

Wenn also ein Instanzobjekt x gegeben ist, versucht der Ausdruck

x += y

ruft x.__iadd__(y) auf, was die in-place Variante von __add__ ist. Wenn __iadd__ nicht vorhanden ist, wird x.__add__(y) versucht, und schließlich y.__radd__(x), wenn __add__ ebenfalls fehlt. Es gibt keine rechte Seite Variante von __iadd__, da dies erfordern würde, dass y weiß, wie x in-place modifiziert wird, was bestenfalls unsicher ist. Der __iadd__ Hook sollte sich ähnlich wie __add__ verhalten und das Ergebnis der Operation zurückgeben (was self sein könnte), das der Variablen x zugewiesen werden soll.

Für C-Erweiterungstypen sind die Hooks Mitglieder der Strukturen PyNumberMethods und PySequenceMethods. Einige spezielle Semantiken gelten, um die Verwendung dieser Methoden und die Vermischung von Python-Instanzobjekten und C-Typen so wenig überraschend wie möglich zu gestalten.

Im allgemeinen Fall von x <augop> y (oder einem ähnlichen Fall unter Verwendung der PyNumber_InPlace API-Funktionen) ist das hauptsächlich operierende Objekt x. Dies unterscheidet sich von normalen binären Operationen, bei denen x und y als kooperierend betrachtet werden könnten, da im Gegensatz zu binären Operationen die Operanden bei einer In-Place-Operation nicht vertauscht werden können. In-Place-Operationen greifen jedoch auf normale binäre Operationen zurück, wenn eine In-Place-Modifikation nicht unterstützt wird, was zu den folgenden Regeln führt

  • Wenn das linke Objekt (x) ein Instanzobjekt ist und eine Methode __coerce__ hat, rufe diese Funktion mit y als Argument auf. Wenn die Koerzierung erfolgreich ist und das resultierende linke Objekt ein anderes Objekt als x ist, stoppe die Verarbeitung als In-Place und rufe die entsprechende Funktion für die normale binäre Operation mit dem koerzierten x und y als Argumenten auf. Das Ergebnis der Operation ist, was auch immer diese Funktion zurückgibt.

    Wenn die Koerzierung kein anderes Objekt für x ergibt, oder x keine Methode __coerce__ definiert, und x den geeigneten __ihook__ für diese Operation hat, rufe diese Methode mit y als Argument auf, und das Ergebnis der Operation ist, was auch immer diese Methode zurückgibt.

  • Andernfalls, wenn das linke Objekt kein Instanzobjekt ist, aber sein Typ die In-Place-Funktion für diese Operation definiert, rufe diese Funktion mit x und y als Argumenten auf, und das Ergebnis der Operation ist, was auch immer diese Funktion zurückgibt.

    Beachten Sie, dass in diesem Fall keine Koerzierung von x oder y erfolgt, und es ist durchaus zulässig, dass ein C-Typ ein Instanzobjekt als zweites Argument erhält; dies ist etwas, das bei normalen binären Operationen nicht vorkommen kann.

  • Andernfalls verarbeite es exakt wie eine normale binäre Operation (nicht In-Place), einschließlich der Argumentkoerzierung. Kurz gesagt, wenn ein Argument ein Instanzobjekt ist, löse die Operation über __coerce__, __hook__ und __rhook__ auf. Andernfalls sind beide Objekte C-Typen, und sie werden koerziert und an die entsprechende Funktion übergeben.
  • Wenn keine Möglichkeit gefunden wird, die Operation zu verarbeiten, löse einen TypeError mit einer auf die Operation zugeschnittenen Fehlermeldung aus.
  • Es gibt einige spezielle Fälle, um den Fall von + und * zu berücksichtigen, die eine besondere Bedeutung für Sequenzen haben: für +, Sequenzkonkatenation, wird keine Koerzierung durchgeführt, wenn ein C-Typ sq_concat oder sq_inplace_concat definiert. Für *, Sequenzwiederholung, wird y in einen C-Integer konvertiert, bevor entweder sq_inplace_repeat und sq_repeat aufgerufen wird. Dies geschieht auch dann, wenn y eine Instanz ist, jedoch nicht, wenn x eine Instanz ist.

Die In-Place-Funktion sollte immer eine neue Referenz zurückgeben, entweder auf das alte x-Objekt, wenn die Operation tatsächlich In-Place ausgeführt wurde, oder auf ein neues Objekt.

Begründung

Es gibt zwei Hauptgründe für die Aufnahme dieses Features in Python: Einfachheit des Ausdrucks und Unterstützung für In-Place-Operationen. Das Endergebnis ist ein Kompromiss zwischen syntaktischer Einfachheit und Ausdruckseinfachheit; wie die meisten neuen Features fügen Augmented Assignments nichts hinzu, was vorher unmöglich war. Es macht diese Dinge lediglich einfacher zu tun.

Das Hinzufügen von Augmented Assignments wird die Python-Syntax komplexer machen. Anstelle einer einzigen Zuweisungsoperation gibt es nun zwölf Zuweisungsoperationen, von denen elf auch eine binäre Operation ausführen. Diese elf neuen Zuweisungsformen sind jedoch leicht als Kopplung zwischen Zuweisung und binärer Operation zu verstehen und erfordern keinen großen konzeptionellen Sprung zum Verständnis. Darüber hinaus haben Sprachen, die Augmented Assignments haben, gezeigt, dass sie ein beliebtes, viel genutztes Feature sind. Ausdrücke der Form

<x> = <x> <operator> <y>

sind in diesen Sprachen häufig genug, um die zusätzliche Syntax zu rechtfertigen, und Python hat nicht signifikant weniger dieser Ausdrücke. Ganz im Gegenteil, denn in Python kann man auch Listen mit einem binären Operator verketten, was recht häufig vorkommt. Das Schreiben des obigen Ausdrucks als

<x> <operator>= <y>

ist sowohl lesbarer als auch weniger fehleranfällig, da es dem Leser sofort ersichtlich ist, dass <x> verändert wird und nicht <x> durch etwas ersetzt wird, das fast, aber nicht ganz, ganz anders als <x> ist.

Die neuen In-Place-Operationen sind besonders nützlich für Matrixberechnungen und andere Anwendungen, die große Objekte erfordern. Um den verfügbaren Programmspeicher effizient zu verwalten, können solche Pakete nicht blindlings die aktuellen binären Operationen verwenden. Da diese Operationen immer ein neues Objekt erstellen, würde das Hinzufügen eines einzelnen Elements zu einem bestehenden (großen) Objekt das Kopieren des gesamten Objekts (was dazu führen kann, dass die Anwendung den Speicher verbraucht), das Hinzufügen des einzelnen Elements und dann möglicherweise das Löschen des ursprünglichen Objekts (abhängig vom Referenzzähler) zur Folge haben.

Um dieses Problem zu umgehen, müssen die Pakete derzeit Methoden oder Funktionen verwenden, um ein Objekt in-place zu modifizieren, was definitiv weniger lesbar ist als ein Augmented Assignment-Ausdruck. Augmented Assignment wird nicht alle Probleme für diese Pakete lösen, da einige Operationen von vornherein nicht durch die begrenzte Menge an binären Operatoren ausgedrückt werden können, aber es ist ein Anfang. PEP 211 befasst sich mit dem Hinzufügen neuer Operatoren.

New methods

Die vorgeschlagene Implementierung fügt die folgenden 11 möglichen Hooks hinzu, die Python-Klassen implementieren können, um die Augmented Assignment-Operationen zu überladen

__iadd__
__isub__
__imul__
__idiv__
__imod__
__ipow__
__ilshift__
__irshift__
__iand__
__ixor__
__ior__

Das i in __iadd__ steht für in-place.

Für C-Erweiterungstypen werden die folgenden Strukturmitglieder hinzugefügt.

Zu PyNumberMethods

binaryfunc nb_inplace_add;
binaryfunc nb_inplace_subtract;
binaryfunc nb_inplace_multiply;
binaryfunc nb_inplace_divide;
binaryfunc nb_inplace_remainder;
binaryfunc nb_inplace_power;
binaryfunc nb_inplace_lshift;
binaryfunc nb_inplace_rshift;
binaryfunc nb_inplace_and;
binaryfunc nb_inplace_xor;
binaryfunc nb_inplace_or;

Zu PySequenceMethods

binaryfunc sq_inplace_concat;
intargfunc sq_inplace_repeat;

Um die binäre Kompatibilität zu wahren, wird das tp_flags TypeObject-Mitglied verwendet, um festzustellen, ob das fragliche TypeObject Speicherplatz für diese Slots zugewiesen hat. Bis eine saubere Unterbrechung der binären Kompatibilität erfolgt (was möglicherweise vor 2.0 geschieht oder auch nicht), muss Code, der eines der neuen Strukturmitglieder verwenden möchte, zuerst prüfen, ob diese mit dem Makro PyType_HasFeature() verfügbar sind.

if (PyType_HasFeature(x->ob_type, Py_TPFLAGS_HAVE_INPLACE_OPS) &&
    x->ob_type->tp_as_number && x->ob_type->tp_as_number->nb_inplace_add) {
        /* ... */

Diese Prüfung muss auch erfolgen, bevor die Methodenslots auf NULL Werte geprüft werden! Das Makro prüft nur, ob die Slots verfügbar sind, nicht, ob sie mit Methoden gefüllt sind oder nicht.

Implementierung

Die aktuelle Implementierung von Augmented Assignment [2] fügt zusätzlich zu den bereits behandelten Methoden und Slots 13 neue Bytecodes und 13 neue API-Funktionen hinzu.

Die API-Funktionen sind einfach In-Place-Versionen der aktuellen Binäroperations-API-Funktionen

PyNumber_InPlaceAdd(PyObject *o1, PyObject *o2);
PyNumber_InPlaceSubtract(PyObject *o1, PyObject *o2);
PyNumber_InPlaceMultiply(PyObject *o1, PyObject *o2);
PyNumber_InPlaceDivide(PyObject *o1, PyObject *o2);
PyNumber_InPlaceRemainder(PyObject *o1, PyObject *o2);
PyNumber_InPlacePower(PyObject *o1, PyObject *o2);
PyNumber_InPlaceLshift(PyObject *o1, PyObject *o2);
PyNumber_InPlaceRshift(PyObject *o1, PyObject *o2);
PyNumber_InPlaceAnd(PyObject *o1, PyObject *o2);
PyNumber_InPlaceXor(PyObject *o1, PyObject *o2);
PyNumber_InPlaceOr(PyObject *o1, PyObject *o2);
PySequence_InPlaceConcat(PyObject *o1, PyObject *o2);
PySequence_InPlaceRepeat(PyObject *o, int count);

Sie rufen entweder die Python-Klassen-Hooks (wenn eines der Objekte eine Python-Klasseninstanz ist) oder die Zahlen- oder Sequenzmethoden des C-Typs auf.

Die neuen Bytecodes sind

INPLACE_ADD
INPLACE_SUBTRACT
INPLACE_MULTIPLY
INPLACE_DIVIDE
INPLACE_REMAINDER
INPLACE_POWER
INPLACE_LEFTSHIFT
INPLACE_RIGHTSHIFT
INPLACE_AND
INPLACE_XOR
INPLACE_OR
ROT_FOUR
DUP_TOPX

Die INPLACE_* Bytecodes spiegeln die BINARY_* Bytecodes wider, mit der Ausnahme, dass sie als Aufrufe der InPlace API-Funktionen implementiert sind. Die beiden anderen Bytecodes sind Utility Bytecodes: ROT_FOUR verhält sich wie ROT_THREE, mit der Ausnahme, dass die vier obersten Stack-Elemente rotiert werden.

DUP_TOPX ist ein Bytecode, der ein einzelnes Argument nimmt, das eine Ganzzahl zwischen 1 und 5 (einschließlich) sein sollte, die Anzahl der Elemente, die in einem Block dupliziert werden sollen. Gegeben sei ein Stack wie dieser (wobei die rechte Seite der Liste der oberste Teil des Stacks ist)

[1, 2, 3, 4, 5]

DUP_TOPX 3 würde die obersten 3 Elemente duplizieren, was zu diesem Stack führt

[1, 2, 3, 4, 5, 3, 4, 5]

DUP_TOPX mit einem Argument von 1 ist dasselbe wie DUP_TOP. Die Grenze von 5 ist rein eine Implementierungsgrenze. Die Implementierung von Augmented Assignment benötigt nur DUP_TOPX mit einem Argument von 2 und 3 und könnte ohne diesen neuen Opcode auskommen, auf Kosten einer beträchtlichen Anzahl von DUP_TOP und ROT_*.

Offene Fragen

Die PyNumber_InPlace API ist nur ein Teilmengen der normalen PyNumber API: nur die Funktionen, die zur Unterstützung der Augmented Assignment-Syntax erforderlich sind, sind enthalten. Wenn andere In-Place API-Funktionen benötigt werden, können sie später hinzugefügt werden.

Der DUP_TOPX Bytecode ist ein Komfort-Bytecode und nicht wirklich notwendig. Es sollte geprüft werden, ob dieser Bytecode sinnvoll ist. Es scheint derzeit keinen anderen möglichen Verwendungszweck für diesen Bytecode zu geben.

Referenzen


Source: https://github.com/python/peps/blob/main/peps/pep-0203.rst

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