PEP 472 – Unterstützung für Indizierung mit Schlüsselwortargumenten
- Autor:
- Stefano Borini, Joseph Martinot-Lagarde
- Discussions-To:
- Python-Ideas Liste
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 24-Jun-2014
- Python-Version:
- 3.6
- Post-History:
- 02-Jul-2014
- Resolution:
- Python-Dev Nachricht
Zusammenfassung
Dieser PEP schlägt eine Erweiterung der Indizierungsoperation vor, um Schlüsselwortargumente zu unterstützen. Notationen in der Form a[K=3,R=2] würden legale Syntax werden. Aus Gründen der Zukunftssicherheit werden a[1:2, K=3, R=4] betrachtet und können je nach Wahl der Implementierung ebenfalls zugelassen werden. Zusätzlich zu einer Änderung im Parser wird auch das Index-Protokoll (__getitem__, __setitem__ und __delitem__) potenziell eine Anpassung erfordern.
Motivation
Die Indizierungssyntax trägt einen starken semantischen Inhalt, der sie von einem Methodenaufruf unterscheidet: sie impliziert sich auf einen Teil der Daten zu beziehen. Wir glauben, dass diese semantische Assoziation wichtig ist und möchten die erlaubten Strategien erweitern, um auf diese Daten zu verweisen.
Als allgemeine Beobachtung hängt die Anzahl der Indizes, die eine Indizierungsoperation benötigt, von der Dimensionalität der Daten ab: eindimensionale Daten (z.B. eine Liste) benötigen einen Index (z.B. a[3]), zweidimensionale Daten (z.B. eine Matrix) benötigen zwei Indizes (z.B. a[2,3]) und so weiter. Jeder Index ist ein Selektor entlang einer der Achsen der Dimensionalität, und die Position im Index-Tupel ist die Metainformation, die benötigt wird, um jeden Index mit der entsprechenden Achse zu verknüpfen.
Die aktuelle Python-Syntax konzentriert sich ausschließlich auf die Position, um die Assoziation mit den Achsen auszudrücken, und enthält auch syntaktischen Zucker, um nicht-punktuelle Auswahl (Slices) zu referenzieren.
>>> a[3] # returns the fourth element of a
>>> a[1:10:2] # slice notation (extract a non-trivial data subset)
>>> a[3,2] # multiple indexes (for multidimensional arrays)
Die in diesem PEP vorgeschlagene zusätzliche Notation würde Notationen mit Schlüsselwortargumenten in der Indizierungsoperation ermöglichen, z.B.
>>> a[K=3, R=2]
was die Referenzierung von Achsen anhand konventioneller Namen ermöglichen würde.
Man muss zusätzlich die erweiterte Form betrachten, die sowohl positionale als auch Schlüsselwortspezifikation erlaubt.
>>> a[3,R=3,K=4]
Dieser PEP wird verschiedene Strategien untersuchen, um die Verwendung dieser Notationen zu ermöglichen.
Anwendungsfälle
Die folgenden praktischen Anwendungsfälle präsentieren zwei breite Kategorien der Nutzung einer Schlüsselwortspezifikation: Indizierung und kontextuelle Option. Für die Indizierung
- Um der Indizierung eine kommunikativere Bedeutung zu geben und z.B. versehentliche Umkehrungen von Indizes zu verhindern.
>>> gridValues[x=3, y=5, z=8] >>> rain[time=0:12, location=location]
- In einigen Bereichen, wie der computergestützten Physik und Chemie, ist die Verwendung einer Notation wie
Basis[Z=5]eine domänenspezifische Sprachnotation zur Darstellung einer Genauigkeitsstufe.>>> low_accuracy_energy = computeEnergy(molecule, BasisSet[Z=3])
In diesem Fall würde die Indizierungsoperation eine Basis-Menge mit der gewählten Genauigkeitsstufe (repräsentiert durch den Parameter Z) zurückgeben. Der Grund für eine Indizierung ist, dass das BasisSet-Objekt intern als numerische Tabelle dargestellt werden könnte, wobei Zeilen (die „Koeffizienten“-Achse, im Beispiel für den Benutzer verborgen) einzelnen Elementen zugeordnet sind (z.B. Zeile 0:5 enthält Koeffizienten für Element 1, Zeile 5:8 Koeffizienten für Element 2) und jede Spalte einer bestimmten Genauigkeitsstufe („Genauigkeit“ oder „Z“-Achse) zugeordnet ist, so dass die erste Spalte geringe Genauigkeit, die zweite mittlere Genauigkeit usw. ist. Mit dieser Indizierung würde der Benutzer ein weiteres Objekt erhalten, das den Inhalt der Spalte der internen Tabelle für die Genauigkeitsstufe 3 darstellt.
Zusätzlich kann die Schlüsselwortspezifikation als Option im Kontext der Indizierung verwendet werden. Insbesondere
- Eine „Standard“-Option ermöglicht die Angabe eines Standardrückgabewerts, wenn der Index nicht vorhanden ist.
>>> lst = [1, 2, 3] >>> value = lst[5, default=0] # value is 0
- Für spärliche Datensätze, um eine Interpolationsstrategie zur Ableitung eines fehlenden Punktes aus z.B. seinen umgebenden Daten anzugeben.
>>> value = array[1, 3, interpolate=spline_interpolator]
- Eine Einheit könnte mit demselben Mechanismus angegeben werden.
>>> value = array[1, 3, unit="degrees"]
Wie die Notation interpretiert wird, liegt im Ermessen der implementierenden Klasse.
Aktuelle Implementierung
Derzeit wird die Indizierungsoperation von den Methoden __getitem__, __setitem__ und __delitem__ gehandhabt. Die Signaturen dieser Methoden akzeptieren ein Argument für den Index (wobei __setitem__ ein zusätzliches Argument für den zu setzenden Wert akzeptiert). Im Folgenden analysieren wir ausschließlich __getitem__(self, idx), wobei die gleichen Überlegungen für die übrigen beiden Methoden gelten.
Wenn eine Indizierungsoperation durchgeführt wird, wird __getitem__(self, idx) aufgerufen. Traditionell wird der gesamte Inhalt zwischen eckigen Klammern in ein einzelnes Objekt umgewandelt, das an das Argument idx übergeben wird.
- Wenn ein einzelnes Element übergeben wird, z.B.
a[2], istidxgleich2. - Wenn mehrere Elemente übergeben werden, müssen sie durch Kommas getrennt werden:
a[2, 3]. In diesem Fall istidxein Tupel(2, 3). Mita[2, 3, "hallo", {}]istidxgleich(2, 3, "hallo", {}). - Eine Slicing-Notation, z.B.
a[2:10], erzeugt ein Slice-Objekt oder ein Tupel, das Slice-Objekte enthält, wenn mehrere Werte übergeben wurden.
Abgesehen von seiner einzigartigen Fähigkeit, Slicing-Notation zu handhaben, hat die Indizierungsoperation Ähnlichkeiten mit einem einfachen Methodenaufruf: sie verhält sich wie einer, wenn sie mit nur einem Element aufgerufen wird; wenn die Anzahl der Elemente größer als eins ist, verhält sich das idx-Argument wie ein *args. Wie im Abschnitt Motivation erwähnt, hat eine Indizierungsoperation jedoch die starke semantische Bedeutung der Extraktion eines Teilmengen aus einer größeren Menge, was einem regulären Methodenaufruf nicht automatisch zugeordnet ist, es sei denn, es wird eine geeignete Benennung gewählt. Darüber hinaus ist sein unterschiedlicher visueller Stil wichtig für die Lesbarkeit.
Spezifikationen
Die Implementierung sollte versuchen, die aktuelle Signatur für __getitem__ beizubehalten oder sie rückwärtskompatibel zu ändern. Wir werden verschiedene Alternativen vorstellen, die die zu berücksichtigenden möglichen Fälle berücksichtigen.
C0. a[1]; a[1,2] # Traditional indexing
C1. a[Z=3]
C2. a[Z=3, R=4]
C3. a[1, Z=3]
C4. a[1, Z=3, R=4]
C5. a[1, 2, Z=3]
C6. a[1, 2, Z=3, R=4]
C7. a[1, Z=3, 2, R=4] # Interposed ordering
Strategie „Strict dictionary“
Diese Strategie erkennt an, dass __getitem__ besonders darin ist, nur ein Objekt zu akzeptieren, und die Natur dieses Objekts muss in seiner Spezifikation der Achsen eindeutig sein: es kann entweder nach Ordnung oder nach Name erfolgen. Als Ergebnis dieser Annahme ist im Beisein von Schlüsselwortargumenten die übergebene Entität ein Dictionary und alle Labels müssen angegeben werden.
C0. a[1]; a[1,2] -> idx = 1; idx = (1, 2)
C1. a[Z=3] -> idx = {"Z": 3}
C2. a[Z=3, R=4] -> idx = {"Z": 3, "R": 4}
C3. a[1, Z=3] -> raise SyntaxError
C4. a[1, Z=3, R=4] -> raise SyntaxError
C5. a[1, 2, Z=3] -> raise SyntaxError
C6. a[1, 2, Z=3, R=4] -> raise SyntaxError
C7. a[1, Z=3, 2, R=4] -> raise SyntaxError
Vorteile
- Starke konzeptionelle Ähnlichkeit zwischen dem Tupel-Fall und dem Dictionary-Fall. Im ersten Fall geben wir ein Tupel an, also definieren wir natürlich eine einfache Menge von durch Kommas getrennten Werten. Im zweiten Fall geben wir ein Dictionary an, also geben wir eine homogene Menge von Schlüssel/Wert-Paaren an, wie in
dict(Z=3, R=4); - Einfach und leicht auf der
__getitem__-Seite zu parsen: Wenn es ein Tupel erhält, bestimmt es die Achsen anhand der Position. Wenn es ein Dictionary erhält, verwendet es die Schlüsselwörter. - Die C-Schnittstelle muss nicht geändert werden.
Neutral
- Degeneration von
a[{"Z": 3, "R": 4}]mita[Z=3, R=4]bedeutet, dass die Notation syntaktischer Zucker ist.
Nachteile
- Sehr streng.
- Zerstört die Reihenfolge der übergebenen Argumente. Das Beibehalten der Reihenfolge wäre mit einem OrderedDict möglich, wie in PEP 468 entworfen.
- Erlaubt keine Anwendungsfälle mit gemischten positional/keyword-Argumenten wie
a[1, 2, default=5].
Strategie „mixed dictionary“
Diese Strategie lockert die obige Einschränkung, um ein Dictionary mit sowohl Zahlen als auch Zeichenketten als Schlüssel zurückzugeben.
C0. a[1]; a[1,2] -> idx = 1; idx = (1, 2)
C1. a[Z=3] -> idx = {"Z": 3}
C2. a[Z=3, R=4] -> idx = {"Z": 3, "R": 4}
C3. a[1, Z=3] -> idx = { 0: 1, "Z": 3}
C4. a[1, Z=3, R=4] -> idx = { 0: 1, "Z": 3, "R": 4}
C5. a[1, 2, Z=3] -> idx = { 0: 1, 1: 2, "Z": 3}
C6. a[1, 2, Z=3, R=4] -> idx = { 0: 1, 1: 2, "Z": 3, "R": 4}
C7. a[1, Z=3, 2, R=4] -> idx = { 0: 1, "Z": 3, 2: 2, "R": 4}
Vorteile
- Ermöglicht gemischte Fälle.
Nachteile
- Zerstört die Reihenfolgeinformationen für Zeichenketten-Schlüssel. Wir haben keine Möglichkeit zu sagen, ob
"Z"in Position 1 oder 3 war. - Impliziert den Wechsel von einem Tupel zu einem Dictionary, sobald ein angegebener Index ein Schlüsselwortargument hat. Könnte verwirrend beim Parsen sein.
Strategie „named tuple“
Gibt ein benanntes Tupel für idx anstelle eines Tupels zurück. Schlüsselwortargumente hätten offensichtlich ihren angegebenen Namen als Schlüssel, und positionale Argumente hätten einen Unterstrich gefolgt von ihrer Reihenfolge.
C0. a[1]; a[1,2] -> idx = 1; idx = (_0=1, _1=2)
C1. a[Z=3] -> idx = (Z=3)
C2. a[Z=3, R=2] -> idx = (Z=3, R=2)
C3. a[1, Z=3] -> idx = (_0=1, Z=3)
C4. a[1, Z=3, R=2] -> idx = (_0=1, Z=3, R=2)
C5. a[1, 2, Z=3] -> idx = (_0=1, _2=2, Z=3)
C6. a[1, 2, Z=3, R=4] -> (_0=1, _1=2, Z=3, R=4)
C7. a[1, Z=3, 2, R=4] -> (_0=1, Z=3, _1=2, R=4)
or (_0=1, Z=3, _2=2, R=4)
or raise SyntaxError
Der erforderliche Typname des benannten Tupels könnte Index oder der Name des Arguments in der Funktionsdefinition sein, er behält die Reihenfolge und ist leicht zu analysieren, indem das Attribut _fields verwendet wird. Er ist abwärtskompatibel, vorausgesetzt, dass C0 mit mehr als einem Eintrag nun ein benanntes Tupel anstelle eines einfachen Tupels übergibt.
Vorteile
- Sieht gut aus. benanntes Tupel ersetzt transparent Tupel und fällt elegant auf das alte Verhalten zurück.
- Erfordert keine Änderung an der C-Schnittstelle.
Nachteile
- Laut einigen Quellen ist namedtuple nicht gut entwickelt. Es als ein so wichtiges Objekt einzubeziehen, würde wahrscheinlich Überarbeitung und Verbesserung erfordern;
- Die Felder des benannten Tupels, und damit der Typ, müssen sich entsprechend den übergebenen Argumenten ändern. Dies kann zu einem Leistungsengpass führen und macht es unmöglich zu garantieren, dass zwei aufeinanderfolgende Indexzugriffe dieselbe Indexklasse erhalten;
- die
_n„magischen“ Felder sind etwas ungewöhnlich, aber IPython verwendet sie bereits für die Ergebnisgeschichte. - Python hat derzeit kein eingebautes benanntes Tupel. Das aktuelle ist im Modul „collections“ in der Standardbibliothek verfügbar.
- Im Gegensatz zu einer Funktion würden die beiden Notationen
gridValues[x=3, y=5, z=8]undgridValues[3,5,8]nicht gut übereinstimmen, wenn die Reihenfolge zur Laufzeit geändert wird (z.B. wir fragen nachgridValues[y=5, z=8, x=3]). In einer Funktion können wir Argumentnamen vordefinieren, damit Schlüsselwortargumente richtig zugeordnet werden. Nicht so in__getitem__, was die Aufgabe der Interpretation und Zuordnung an__getitem__selbst überlässt.
Strategie „New argument contents“
In der aktuellen Implementierung werden, wenn viele Argumente an __getitem__ übergeben werden, diese in einem Tupel gruppiert und dieses Tupel wird an __getitem__ als einzelnes Argument idx übergeben. Diese Strategie behält die aktuelle Signatur bei, erweitert aber den Variationsbereich in Typ und Inhalt von idx auf komplexere Darstellungen.
Wir identifizieren vier mögliche Wege, diese Strategie zu implementieren.
- P1: verwendet ein einzelnes Dictionary für die Schlüsselwortargumente.
- P2: verwendet einzelne Dictionaries mit jeweils einem Element.
- P3: ähnlich wie P2, ersetzt aber einzelne Dictionaries durch ein
(key, value)-Tupel. - P4: ähnlich wie P2, verwendet aber ein spezielles und zusätzliches neues Objekt:
keyword()
Einige dieser Möglichkeiten führen zu degenerierten Notationen, d.h. ununterscheidbar von einer bereits möglichen Darstellung. Wieder einmal wird die vorgeschlagene Notation zu syntaktischem Zucker für diese Darstellungen.
Unter dieser Strategie bleibt das alte Verhalten für C0 unverändert.
C0: a[1] -> idx = 1 # integer
a[1,2] -> idx = (1,2) # tuple
In C1 können wir entweder ein Dictionary oder ein Tupel verwenden, um Schlüssel und Wertpaar für den spezifischen Indizierungseintrag darzustellen. Wir müssen ein Tupel mit einem Tupel in C1 haben, da wir sonst a["Z", 3] nicht von a[Z=3] unterscheiden können.
C1: a[Z=3] -> idx = {"Z": 3} # P1/P2 dictionary with single key
or idx = (("Z", 3),) # P3 tuple of tuples
or idx = keyword("Z", 3) # P4 keyword object
Wie Sie sehen können, implizieren die Notationen P1/P2, dass a[Z=3] und a[{"Z": 3}] __getitem__ mit genau demselben Wert aufrufen und daher syntaktischer Zucker für letzteres sind. Die gleiche Situation tritt, wenn auch mit einem anderen Index, für P3 auf. Die Verwendung eines Schlüsselwortobjekts wie in P4 würde diese Degeneration beseitigen.
Für den C2-Fall
C2. a[Z=3, R=4] -> idx = {"Z": 3, "R": 4} # P1 dictionary/ordereddict
or idx = ({"Z": 3}, {"R": 4}) # P2 tuple of two single-key dict
or idx = (("Z", 3), ("R", 4)) # P3 tuple of tuples
or idx = (keyword("Z", 3),
keyword("R", 4) ) # P4 keyword objects
P1 bildet sich natürlich auf das traditionelle **kwargs-Verhalten ab, bricht aber die Konvention, dass zwei oder mehr Einträge für den Index ein Tupel ergeben. P2 behält dieses Verhalten bei und bewahrt zusätzlich die Reihenfolge. Das Beibehalten der Reihenfolge wäre auch mit einem OrderedDict möglich, wie in PEP 468 entworfen.
Die verbleibenden Fälle werden hier gezeigt.
C3. a[1, Z=3] -> idx = (1, {"Z": 3}) # P1/P2
or idx = (1, ("Z", 3)) # P3
or idx = (1, keyword("Z", 3)) # P4
C4. a[1, Z=3, R=4] -> idx = (1, {"Z": 3, "R": 4}) # P1
or idx = (1, {"Z": 3}, {"R": 4}) # P2
or idx = (1, ("Z", 3), ("R", 4)) # P3
or idx = (1, keyword("Z", 3),
keyword("R", 4)) # P4
C5. a[1, 2, Z=3] -> idx = (1, 2, {"Z": 3}) # P1/P2
or idx = (1, 2, ("Z", 3)) # P3
or idx = (1, 2, keyword("Z", 3)) # P4
C6. a[1, 2, Z=3, R=4] -> idx = (1, 2, {"Z":3, "R": 4}) # P1
or idx = (1, 2, {"Z": 3}, {"R": 4}) # P2
or idx = (1, 2, ("Z", 3), ("R", 4)) # P3
or idx = (1, 2, keyword("Z", 3),
keyword("R", 4)) # P4
C7. a[1, Z=3, 2, R=4] -> idx = (1, 2, {"Z": 3, "R": 4}) # P1. Pack the keyword arguments. Ugly.
or raise SyntaxError # P1. Same behavior as in function calls.
or idx = (1, {"Z": 3}, 2, {"R": 4}) # P2
or idx = (1, ("Z", 3), 2, ("R", 4)) # P3
or idx = (1, keyword("Z", 3),
2, keyword("R", 4)) # P4
Vorteile
- Signatur ist unverändert;
- P2/P3 können die Reihenfolge der Schlüsselwortargumente beibehalten, wie sie bei der Indizierung angegeben wurden,
- P1 benötigt ein OrderedDict, würde aber die zwischengeschaltete Reihenfolge zerstören, wenn sie erlaubt wäre: alle Schlüsselwortindizes würden in das Dictionary dumpen;
- Bleibt innerhalb traditioneller Typen: Tupel und Dictionaries. Eventuell OrderedDict;
- Einige vorgeschlagene Strategien sind im Verhalten einer traditionellen Funktionsaufrufung ähnlich;
- Die C-Schnittstelle für
PyObject_GetItemund Familie würde unverändert bleiben.
Nachteile
- Offensichtlich komplex und verschwenderisch;
- Degeneration in der Notation (z.B.
a[Z=3]unda[{"Z":3}]sind auf der Ebene von__[get|set|del]item__äquivalente und ununterscheidbare Notationen). Dieses Verhalten ist möglicherweise akzeptabel oder nicht. - Für P4 ist ein zusätzliches Objekt, ähnlich dem von slice(), erforderlich, aber nur zur Entschärfung der obigen Degeneration.
- Typ und Layout von
idxscheinen sich je nach Launen des Aufrufers zu ändern; - Kann komplex zu parsen sein, was übergeben wird, insbesondere im Fall von Tupeln von Tupeln;
- P2 Erzeugt viele Dictionaries mit einzelnen Schlüsseln als Mitglieder eines Tupels. Sieht hässlich aus. P3 wäre leichter und einfacher zu verwenden als das Tupel von Dictionaries und behält immer noch die Reihenfolge bei (im Gegensatz zum regulären Dictionary), würde aber zu einer umständlichen Extraktion von Schlüsselwörtern führen.
Strategie „kwargs argument“
Akzeptiert ein optionales **kwargs-Argument, das nur Schlüsselwort sein sollte. idx wird ebenfalls optional, um einen Fall zu unterstützen, in dem keine nicht-schlüsselwort Argumente erlaubt sind. Die Signatur wäre dann entweder
__getitem__(self, idx)
__getitem__(self, idx, **kwargs)
__getitem__(self, **kwargs)
Angewendet auf unsere Fälle würde ergeben
C0. a[1,2] -> idx=(1,2); kwargs={}
C1. a[Z=3] -> idx=None ; kwargs={"Z":3}
C2. a[Z=3, R=4] -> idx=None ; kwargs={"Z":3, "R":4}
C3. a[1, Z=3] -> idx=1 ; kwargs={"Z":3}
C4. a[1, Z=3, R=4] -> idx=1 ; kwargs={"Z":3, "R":4}
C5. a[1, 2, Z=3] -> idx=(1,2); kwargs={"Z":3}
C6. a[1, 2, Z=3, R=4] -> idx=(1,2); kwargs={"Z":3, "R":4}
C7. a[1, Z=3, 2, R=4] -> raise SyntaxError # in agreement to function behavior
Leere Indizierung a[] bleibt natürlich ungültige Syntax.
Vorteile
- Ähnlich wie Funktionsaufruf, entwickelt sich natürlich daraus;
- Die Verwendung von Schlüsselwortindizierung mit einem Objekt, dessen
__getitem__keine kwargs hat, wird offensichtlich fehlschlagen. Das ist bei den anderen Strategien nicht der Fall.
Nachteile
- Es bewahrt die Reihenfolge nicht, es sei denn, ein OrderedDict wird verwendet;
- Verbietet C7, aber ist das wirklich nötig?
- Erfordert eine Änderung an der C-Schnittstelle, um ein zusätzliches PyObject für die Schlüsselwortargumente zu übergeben.
C-Schnittstelle
Wie bereits in der vorherigen Analyse kurz erwähnt, müsste die C-Schnittstelle möglicherweise geändert werden, um die neue Funktion zu ermöglichen. Insbesondere müssten PyObject_GetItem und verwandte Routinen ein zusätzliches Argument PyObject *kw für die Strategie „kwargs argument“ akzeptieren. Die übrigen Strategien würden keine Änderung der C-Funktionssignaturen erfordern, aber die unterschiedliche Natur des übergebenen Objekts würde möglicherweise eine Anpassung erfordern.
Die Strategie „named tuple“ würde ohne Änderungen korrekt funktionieren: Die von der Factory-Methode in collections zurückgegebene Klasse gibt eine Unterklasse von Tupel zurück, was bedeutet, dass PyTuple_*-Funktionen das resultierende Objekt verarbeiten können.
Alternative Lösungen
In diesem Abschnitt präsentieren wir alternative Lösungen, die die fehlende Funktion umgehen würden und die vorgeschlagene Verbesserung als nicht implementierungswürdig erscheinen lassen.
Verwende eine Methode
Man könnte die Indizierung so belassen, wie sie ist, und eine traditionelle get()-Methode für Fälle verwenden, in denen die einfache Indizierung nicht ausreicht. Dies ist ein guter Punkt, aber wie bereits in der Einleitung erwähnt, haben Methoden ein anderes semantisches Gewicht als die Indizierung, und man kann Slices nicht direkt in Methoden verwenden. Vergleichen Sie z.B. a[1:3, Z=2] mit a.get(slice(1,3), Z=2).
Die Autoren erkennen dieses Argument jedoch als zwingend an, und der Vorteil der semantischen Ausdruckskraft einer schlüsselwortbasierten Indizierung mag durch eine selten genutzte Funktion, die keinen ausreichenden Nutzen bringt und eine begrenzte Akzeptanz haben könnte, aufgewogen werden.
Emuliere das angeforderte Verhalten durch Missbrauch des Slice-Objekts
Diese äußerst kreative Methode nutzt das Verhalten von Slice-Objekten, vorausgesetzt, man akzeptiert die Verwendung von Zeichenketten (oder instanziiert ordnungsgemäß benannte Platzhalterobjekte für die Schlüssel) und akzeptiert die Verwendung von „:“ anstelle von „=“.
>>> a["K":3]
slice('K', 3, None)
>>> a["K":3, "R":4]
(slice('K', 3, None), slice('R', 4, None))
>>>
Obwohl eindeutig raffiniert, erlaubt dieser Ansatz keine einfache Abfrage des Schlüssel/Wert-Paares, ist zu clever und esoterisch und erlaubt keine Übergabe eines Slices wie in a[K=1:10:2].
Tim Delaney kommentiert jedoch
„Ich denke wirklich, dassa[b=c, d=e]einfach nur syntaktischer Zucker füra['b':c, 'd':e]sein sollte. Es ist einfach zu erklären und bietet die größte Rückwärtskompatibilität. Insbesondere Bibliotheken, die Slices bereits auf diese Weise missbraucht haben, werden mit der neuen Syntax weiterhin funktionieren.“
Wir glauben, dass dieses Verhalten zu unbequemen Ergebnissen führen würde. Die Bibliothek Pandas verwendet Zeichenketten als Bezeichnungen und erlaubt Notationen wie
>>> a[:, "A":"F"]
um Daten aus Spalte „A“ bis Spalte „F“ zu extrahieren. Gemäß dem obigen Kommentar würde diese Notation gleichermaßen erhalten mit
>>> a[:, A="F"]
was seltsam ist und mit der beabsichtigten Bedeutung von Schlüsselwörtern bei der Indizierung kollidiert, nämlich der Spezifizierung der Achse durch konventionelle Namen anstelle von Positionen.
Übergib ein Dictionary als zusätzlichen Index
>>> a[1, 2, {"K": 3}]
Diese Notation, obwohl weniger elegant, kann bereits verwendet werden und erzielt ähnliche Ergebnisse. Es ist offensichtlich, dass die vorgeschlagene Strategie „New argument contents“ als syntaktischer Zucker für diese Notation interpretiert werden kann.
Zusätzliche Kommentare
Kommentatoren äußerten auch folgende relevante Punkte.
Relevanz der Reihenfolge von Schlüsselwortargumenten
Im Rahmen der Diskussion dieses PEP ist es wichtig zu entscheiden, ob die Reihenfolgeinformation der Schlüsselwortargumente wichtig ist und ob Indizes und Schlüssel beliebig geordnet werden können (z.B. a[1,Z=3,2,R=4]). PEP 468 versucht, den ersten Punkt anzugehen, indem die Verwendung eines ordereddict vorgeschlagen wird, aber man wäre geneigt zu akzeptieren, dass Schlüsselwortargumente bei der Indizierung äquivalent zu kwargs bei Funktionsaufrufen sind und daher heute ebenso ungeordnet sind und die gleichen Einschränkungen haben.
Bedarf an Homogenität des Verhaltens
Im Verhältnis zur Strategie „New argument contents“ weist ein Kommentar von Ian Cordasco darauf hin, dass
„wäre es unzumutbar, wenn nur eine Methode sich völlig anders verhält als das Standardverhalten in Python. Es wäre verwirrend, wenn nur__getitem__(und angeblich auch__setitem__) Schlüsselwortargumente akzeptiert, diese aber anstatt in ein Dictionary umzuwandeln, in einzelne Dictionaries mit einem Element umwandelt.“ Wir stimmen seinem Punkt zu; es muss jedoch darauf hingewiesen werden, dass__getitem__in Bezug auf übergebene Argumente bereits in gewisser Weise besonders ist.
Chris Angelico erklärt ebenfalls
„es erscheint sehr seltsam, damit zu beginnen, zu sagen „hier, geben wir der Indizierung die Option, Schlüsselwort-Args zu tragen, genau wie bei Funktionsaufrufen“, und dann zurückzukommen und zu sagen „oh, aber anders als bei Funktionsaufrufen, sind sie inhärent geordnet und werden ganz anders getragen.““ Dem stimmen wir auch zu. Die geradlinigste Strategie, die Homogenität zu wahren, wäre die Strategie „kwargs argument“, die ein**kwargs-Argument für__getitem__öffnet.
Einer der Autoren (Stefano Borini) meint, dass nur die „strict dictionary“-Strategie implementierungswürdig ist. Sie ist eindeutig, einfach, zwingt keine komplexen Parsings auf und löst das Problem der Referenzierung von Achsen entweder nach Position oder nach Namen. Der Anwendungsfall „Options“ wird wahrscheinlich besser mit einem anderen Ansatz gehandhabt und ist für diesen PEP möglicherweise irrelevant. Die alternative „named tuple“ ist eine weitere valide Wahl.
Verschwinden von .get() für Indizierung mit Standardfallback
Die Einführung eines „default“-Schlüsselworts könnte dict.get() obsolet machen, was durch d["key", default=3] ersetzt würde. Chris Angelico erklärt jedoch
„Derzeit müssen Sie__getitem__(das eine Ausnahme auslöst, wenn ein Problem gefunden wird) plus etwas anderes schreiben, z.B.get(), das stattdessen einen Standardwert zurückgibt. Mit Ihrem Vorschlag würden beide Zweige innerhalb von__getitem__gehen, was bedeutet, dass sie Code teilen könnten; aber es müssen immer noch zwei Zweige existieren.“
Zusätzlich fährt Chris fort
„Es wird eine Ad-hoc- und ziemlich willkürliche Pfütze von Namen geben (einige werdendefault=verwenden, andere werden sagen, das ist viel zu lang und gehen zudef=, außer dass das ein Schlüsselwort ist, also werden siedflt=oder etwas Ähnliches verwenden...), es sei denn, es gibt eine starke Kraft, die die Leute zu einem einheitlichen Namen drängt.“.
Dieses Argument ist gültig, aber es ist gleichermaßen gültig für jeden Funktionsaufruf und wird im Allgemeinen durch etablierte Konventionen und Dokumentation behoben.
Zur Degeneration der Notation
Benutzer Drekin kommentierte: „Der Fall a[Z=3] und a[{"Z": 3}] ähnelt dem aktuellen a[1, 2] und a[(1, 2)]. Auch wenn man argumentieren könnte, dass die Klammern tatsächlich nicht Teil der Tupelnotation sind, sondern nur wegen der Syntax benötigt werden, mag es als Degeneration der Notation im Vergleich zum Funktionsaufruf erscheinen: f(1, 2) ist nicht dasselbe wie f((1, 2)).“.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0472.rst
Zuletzt geändert: 2024-12-19 20:04:05 GMT