PEP 637 – Unterstützung für Indizierung mit Schlüsselwortargumenten
- Autor:
- Stefano Borini
- Sponsor:
- Steven D’Aprano
- Discussions-To:
- Python-Ideas Liste
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 24-Aug-2020
- Python-Version:
- 3.10
- Post-History:
- 23-Sep-2020
- Resolution:
- Python-Dev thread
Hinweis
Diese PEP wurde abgelehnt. Im Allgemeinen wurden die Kosten für die Einführung neuer Syntax durch den wahrgenommenen Nutzen nicht aufgewogen. Einzelheiten finden Sie im Link im Feld „Resolution Header“.
Zusammenfassung
Derzeit sind Schlüsselwortargumente bei Funktionsaufrufen erlaubt, aber nicht beim Elementzugriff. Diese PEP schlägt vor, Python um die Zulassung von Schlüsselwortargumenten beim Elementzugriff zu erweitern.
Das folgende Beispiel zeigt Schlüsselwortargumente für normale Funktionsaufrufe
>>> val = f(1, 2, a=3, b=4)
Der Vorschlag würde die Syntax erweitern, um eine ähnliche Konstruktion wie bei Indizierungsoperationen zuzulassen
>>> val = x[1, 2, a=3, b=4] # getitem
>>> x[1, 2, a=3, b=4] = val # setitem
>>> del x[1, 2, a=3, b=4] # delitem
und würde auch entsprechende Semantik bereitstellen. Ein- und doppelte Stern-Entpackung von Argumenten ist ebenfalls vorgesehen
>>> val = x[*(1, 2), **{a=3, b=4}] # Equivalent to above.
Diese PEP ist ein Nachfolger von PEP 472, die 2019 mangels Interesse abgelehnt wurde. Seitdem gibt es ein erneutes Interesse an der Funktion.
Übersicht
Hintergrund
PEP 472 wurde 2014 eröffnet. Die PEP detaillierte verschiedene Anwendungsfälle und wurde durch Extraktion von Implementierungsstrategien aus einer breiten Diskussion auf der python-ideas-Mailingliste erstellt, obwohl kein klarer Konsens darüber erzielt wurde, welche Strategie verwendet werden sollte. Viele Sonderfälle wurden genauer untersucht und fühlten sich umständlich, rückwärtsinkompatibel oder beides an.
Die PEP wurde schließlich 2019 [1] abgelehnt, hauptsächlich aufgrund mangelnden Interesses an der Funktion trotz ihres 5-jährigen Bestehens.
Mit der Einführung von Typ-Hints in PEP 484 wurde die eckige Klammersyntax jedoch durchgängig verwendet, um Typannotationen zu bereichern, z. B. um eine Liste von Ganzzahlen als Sequence[int] anzugeben. Darüber hinaus gab es ein erweitertes Wachstum von Paketen für Datenanalyse wie pandas und xarray, die Namen zur Beschreibung von Spalten in einer Tabelle (pandas) oder Achsen in einem nd-Array (xarray) verwenden. Diese Pakete ermöglichen es Benutzern, auf bestimmte Daten über Namen zuzugreifen, können aber derzeit diese Funktionalität nicht über die Indexnotation ([]) nutzen.
Infolgedessen wurde gelegentlich in vielen verschiedenen Threads auf python-ideas ein erneutes Interesse an einer flexibleren Syntax geäußert, die benannte Informationen zulässt, zuletzt von Caleb Donovick [2] im Jahr 2019 und Andras Tantos [3] im Jahr 2020. Diese Anfragen lösten eine starke Aktivität auf der python-ideas-Mailingliste aus, wo die verschiedenen Optionen erneut diskutiert wurden und nun ein allgemeiner Konsens über eine Implementierungsstrategie erzielt wurde.
Anwendungsfälle
Die folgenden praktischen Anwendungsfälle zeigen verschiedene Fälle, in denen eine Schlüsselwortspezifikation die Notation verbessern und zusätzlichen Wert bieten würde
- Um dem Index eine kommunikativere Bedeutung zu verleihen und z. B. versehentliche Vertauschung von Indizes zu verhindern
>>> grid_position[x=3, y=5, z=8] >>> rain_amount[time=0:12, location=location] >>> matrix[row=20, col=40]
- Um die Tippnotation mit Schlüsselwörtern anzureichern, insbesondere bei der Verwendung von Generika
def function(value: MyType[T=int]):
- In einigen Bereichen wie der Computerphysik und -chemie ist die Verwendung einer Notation wie
Basis[Z=5]eine domänenspezifische Sprachnotation zur Darstellung eines Genauigkeitsgrades>>> low_accuracy_energy = computeEnergy(molecule, BasisSet[Z=3])
- Pandas verwendet derzeit eine Notation wie
>>> df[df['x'] == 1]
was durch
df[x=1]ersetzt werden könnte. - xarray hat benannte Dimensionen. Derzeit werden diese mit den Funktionen .isel behandelt
>>> data.isel(row=10) # Returns the tenth row
was auch durch
data[row=10]ersetzt werden könnte. Ein komplexeres Beispiel>>> # old syntax >>> da.isel(space=0, time=slice(None, 2))[...] = spam >>> # new syntax >>> da[space=0, time=:2] = spam
Ein weiteres Beispiel
>>> # old syntax >>> ds["empty"].loc[dict(lon=5, lat=6)] = 10 >>> # new syntax >>> ds["empty"][lon=5, lat=6] = 10 >>> # old syntax >>> ds["empty"].loc[dict(lon=slice(1, 5), lat=slice(3, None))] = 10 >>> # new syntax >>> ds["empty"][lon=1:5, lat=6:] = 10
- Funktionen/Methoden, deren Argument eine andere Funktion ist (plus deren Argumente), benötigen eine Möglichkeit, zu bestimmen, welche Argumente für die Ziel-Funktion bestimmt sind und welche zur Konfiguration ihrer Ausführung verwendet werden. Dies ist einfach (wenn auch nicht erweiterbar) für Positionsargumente, aber wir benötigen eine Möglichkeit, diese für Schlüsselwörter zu unterscheiden. [4]
Eine indizierte Notation würde eine Python-konforme Möglichkeit bieten, Schlüsselwortargumente an diese Funktionen zu übergeben, ohne den Code des Aufrufers zu überladen.
>>> # Let's start this example with basic syntax without keywords. >>> # the positional values are arguments to `func` while >>> # `name=` is processed by `trio.run`. >>> trio.run(func, value1, value2, name="func") >>> # `trio.run` ends up calling `func(value1, value2)`. >>> # If we want/need to pass value2 by keyword (keyword-only argument, >>> # additional arguments that won't break backwards compatibility ...), >>> # currently we need to resort to functools.partial: >>> trio.run(functools.partial(func, param2=value2), value1, name="func") >>> trio.run(functools.partial(func, value1, param2=value2), name="func") >>> # One possible workaround is to convert `trio.run` to an object >>> # with a `__call__` method, and use an "option" helper, >>> trio.run.option(name="func")(func, value1, param2=value2) >>> # However, foo(bar)(baz) is uncommon and thus disruptive to the reader. >>> # Also, you need to remember the name of the `option` method. >>> # This PEP allows us to replace `option` with `__getitem__`. >>> # The call is now shorter, more mnemonic, and looks+works like typing >>> trio.run[name="func"](func, value1, param2=value2)
- Die Verfügbarkeit von Sternargumenten würde PEP 646 Variadic Generics zugute kommen, insbesondere in den Formen
a[*x]unda[*x, *y, p, q, *z]. Die PEP beschreibt genau diese Notation in ihrem Abschnitt „Unpacking: Star Operator“.
Es ist wichtig zu beachten, dass die Interpretation der Notation der Implementierung überlassen bleibt. Diese PEP definiert und diktiert nur das Verhalten von Python in Bezug auf übergebene Schlüsselwortargumente, nicht, wie diese Argumente von der implementierenden Klasse interpretiert und verwendet werden sollten.
Aktueller Status der Indizierungsoperation
Bevor die neue Syntax und Semantik für die Indizierungsnotation detailliert beschrieben wird, ist es relevant zu analysieren, wie die Indizierungsnotation heute funktioniert, in welchen Kontexten und wie sie sich von einem Funktionsaufruf unterscheidet.
Subscripting obj[x] ist effektiv eine alternative und spezialisierte Form der Funktionsaufrufsyntax mit einer Reihe von Unterschieden und Einschränkungen im Vergleich zu obj(x). Die aktuelle Python-Syntax konzentriert sich ausschließlich auf die Position zur Angabe des Indexes und enthält auch syntaktischen Zucker, um nicht-punktuelle Auswahlen (Slices) zu referenzieren. Einige gängige Beispiele
>>> 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)
Dies wird in einen __(get|set|del)item__ Dunder-Aufruf übersetzt, an den ein einzelner Parameter mit dem Index (für __getitem__ und __delitem__) oder zwei Parameter mit Index und Wert (für __setitem__) übergeben wird.
Das Verhalten des Indizierungsaufrufs unterscheidet sich in verschiedenen Aspekten grundlegend von einem Funktionsaufruf
Der erste Unterschied liegt in der Bedeutung für den Leser. Ein Funktionsaufruf bedeutet „beliebiger Funktionsaufruf mit potenziellen Seiteneffekten“. Eine Indizierungsoperation bedeutet „Nachschlagen“, typischerweise um auf eine Teilmenge oder einen spezifischen Teilaspekt einer Entität zu zeigen (wie im Fall der Tippnotation). Dieser grundlegende Unterschied bedeutet, dass, obwohl wir Missbrauch nicht verhindern können, Implementierer sich bewusst sein sollten, dass die Einführung von Schlüsselwortargumenten zur Änderung des Nachschlageverhaltens gegen diese intrinsische Bedeutung verstoßen kann.
Der zweite Unterschied der Indizierungsnotation im Vergleich zu einer Funktion besteht darin, dass die Indizierung sowohl für Lese- als auch für Schreibvorgänge verwendet werden kann. In Python kann eine Funktion nicht auf der linken Seite einer Zuweisung stehen. Mit anderen Worten, beides ist gültig
>>> x = a[1, 2]
>>> a[1, 2] = 5
aber nur das erste dieser beiden ist gültig
>>> x = f(1, 2)
>>> f(1, 2) = 5 # invalid
Diese Asymmetrie ist wichtig und lässt einen verstehen, dass es ein natürliches Ungleichgewicht zwischen den beiden Formen gibt. Es ist daher nicht gegeben, dass die beiden transparent und symmetrisch funktionieren sollten.
Der dritte Unterschied besteht darin, dass Funktionen Namen für ihre Argumente zugewiesen haben, es sei denn, die übergebenen Parameter werden mit *args erfasst, in welchem Fall sie als Einträge im args-Tupel landen. Mit anderen Worten, Funktionen haben bereits anonyme Argumentsemantik, genau wie die Indizierungsoperation. Allerdings erhält __(get|set|del)item__ nicht immer ein Tupel als index-Argument (um im Verhalten mit *args einheitlich zu sein). Tatsächlich wird bei einer trivialen Klasse
class X:
def __getitem__(self, index):
print(index)
Die Indexoperation leitet den Inhalt der eckigen Klammern praktisch „wie er ist“ an das index-Argument weiter
>>> x=X()
>>> x[0]
0
>>> x[0, 1]
(0, 1)
>>> x[(0, 1)]
(0, 1)
>>>
>>> x[()]
()
>>> x[{1, 2, 3}]
{1, 2, 3}
>>> x["hello"]
hello
>>> x["hello", "hi"]
('hello', 'hi')
Der vierte Unterschied besteht darin, dass die Indizierungsoperation dank Unterstützung durch den Parser weiß, wie sie Doppelpunkt-Notation in Slices umwandelt. Dies ist gültig
a[1:3]
dieses hier nicht
f(1:3)
Der fünfte Unterschied besteht darin, dass es keine Null-Argument-Form gibt. Dies ist gültig
f()
dieses hier nicht
a[]
Spezifikation
Bevor die Spezifikation beschrieben wird, ist es wichtig, den Unterschied in der Nomenklatur zwischen *Positionsindex*, *finalem Index* und *Schlüsselwortargument* hervorzuheben, da es wichtig ist, die fundamentalen Asymmetrien zu verstehen. __(get|set|del)item__ ist im Grunde eine Indizierungsoperation, und die Art und Weise, wie das Element abgerufen, gesetzt oder gelöscht wird, geschieht über einen Index, den *finalen Index*.
Der aktuelle Status quo ist, den *finalen Index* direkt aus dem, was zwischen eckigen Klammern übergeben wird, dem *Positionsindex*, zu erstellen. Mit anderen Worten, was in den eckigen Klammern übergeben wird, wird trivial verwendet, um das zu generieren, was der Code in __getitem__ dann für die Indizierungsoperation verwendet. Wie wir bereits für das dict gesehen haben, hat d[1] einen Positionsindex von 1 und auch einen finalen Index von 1 (weil es das Element ist, das dann zum Dictionary hinzugefügt wird), und d[1, 2] hat einen Positionsindex von (1, 2) und einen finalen Index ebenfalls von (1, 2) (weil es erneut das Element ist, das zum Dictionary hinzugefügt wird). Der Positionsindex d[1,2:3] wird jedoch vom Dictionary nicht akzeptiert, da es keine Möglichkeit gibt, den Positionsindex in einen finalen Index umzuwandeln, da das Slice-Objekt nicht hashbar ist. Der Positionsindex ist das, was derzeit als index-Parameter in __getitem__ bekannt ist. Nichtsdestotrotz hindert nichts daran, eine dictionary-ähnliche Klasse zu erstellen, die den finalen Index erzeugt, indem sie z. B. den Positionsindex in eine Zeichenkette umwandelt.
Diese PEP erweitert den aktuellen Status Quo und gewährt mehr Flexibilität, den finalen Index über eine erweiterte Syntax zu erstellen, die den Positionsindex und Schlüsselwortargumente kombiniert, falls übergeben.
Das Obige bringt einen wichtigen Punkt hervor. Schlüsselwortargumente können im Kontext der Indexoperation verwendet werden, um Indexierungsentscheidungen zur Erlangung des finalen Index zu treffen, und müssen daher Werte akzeptieren, die für Funktionen unkonventionell sind. Siehe zum Beispiel Anwendungsfall 1, bei dem ein Slice akzeptiert wird.
Die erfolgreiche Implementierung dieser PEP führt zu folgendem Verhalten
- Ein leerer Subscript ist weiterhin illegal, unabhängig vom Kontext (siehe Abgelehnte Ideen)
obj[] # SyntaxError
- Ein einzelner Indexwert bleibt ein einzelner Indexwert, wenn er übergeben wird
obj[index] # calls type(obj).__getitem__(obj, index) obj[index] = value # calls type(obj).__setitem__(obj, index, value) del obj[index] # calls type(obj).__delitem__(obj, index)
Dies gilt auch dann, wenn der Index von Schlüsselwörtern gefolgt wird; siehe Punkt 5 unten.
- Komma-getrennte Argumente werden weiterhin als Tupel geparst und als einzelnes Positionsargument übergeben
obj[spam, eggs] # calls type(obj).__getitem__(obj, (spam, eggs)) obj[spam, eggs] = value # calls type(obj).__setitem__(obj, (spam, eggs), value) del obj[spam, eggs] # calls type(obj).__delitem__(obj, (spam, eggs))
Die oben genannten Punkte bedeuten, dass Klassen, die keine Schlüsselwortargumente in Subscripts unterstützen möchten, nichts tun müssen und das Feature daher vollständig abwärtskompatibel ist.
- Schlüsselwortargumente, falls vorhanden, müssen auf Positionsargumente folgen
obj[1, 2, spam=None, 3] # SyntaxError
Dies ist wie bei Funktionsaufrufen, wo die Vermischung von Positions- und Schlüsselwortargumenten zu einem SyntaxError führt.
- Schlüsselwort-Subscripts, falls vorhanden, werden wie bei Funktionsaufrufen behandelt. Beispiele
# Single index with keywords: obj[index, spam=1, eggs=2] # calls type(obj).__getitem__(obj, index, spam=1, eggs=2) obj[index, spam=1, eggs=2] = value # calls type(obj).__setitem__(obj, index, value, spam=1, eggs=2) del obj[index, spam=1, eggs=2] # calls type(obj).__delitem__(obj, index, spam=1, eggs=2) # Comma-separated indices with keywords: obj[foo, bar, spam=1, eggs=2] # calls type(obj).__getitem__(obj, (foo, bar), spam=1, eggs=2) obj[foo, bar, spam=1, eggs=2] = value # calls type(obj).__setitem__(obj, (foo, bar), value, spam=1, eggs=2) del obj[foo, bar, spam=1, eggs=2] # calls type(obj).__detitem__(obj, (foo, bar), spam=1, eggs=2)
Beachten Sie, dass
- ein einzelner Positionsindex nicht in ein Tupel umgewandelt wird, nur weil ein Schlüsselwort hinzugefügt wird.
- für
__setitem__wird die gleiche Reihenfolge für Index und Wert beibehalten. Die Schlüsselwortargumente kommen am Ende, wie es bei einer Funktionsdefinition üblich ist.
- Die gleichen Regeln gelten für Schlüsselwort-Subscripts wie für Schlüsselwörter bei Funktionsaufrufen
- der Interpreter ordnet jedes Schlüsselwort-Subscript einem benannten Parameter in der entsprechenden Methode zu;
- wenn ein benannter Parameter zweimal verwendet wird, ist dies ein Fehler;
- wenn nach der Verwendung aller Schlüsselwörter noch benannte Parameter übrig sind (ohne Wert), erhalten sie ihren Standardwert (falls vorhanden);
- wenn ein solcher Parameter keinen Standardwert hat, ist dies ein Fehler;
- wenn nach dem Auffüllen aller benannten Parameter noch Schlüsselwort-Subscripts übrig sind und die Methode einen
**kwargs-Parameter hat, werden diese als Dict an den**kwargs-Parameter gebunden; - aber wenn kein
**kwargs-Parameter definiert ist, ist dies ein Fehler.
- Sequenz-Unpacking ist innerhalb von Subscripts erlaubt
obj[*items]
Dies ermöglicht Notationen wie
[:, *args, :], die als[(slice(None), *args, slice(None))]behandelt werden könnten. Mehrfach-Stern-Unpacking ist erlaubtobj[1, *(2, 3), *(4, 5), 6, foo=5] # Equivalent to obj[(1, 2, 3, 4, 5, 6), foo=3)
Die folgende Notation-Äquivalenz muss eingehalten werden
obj[*()] # Equivalent to obj[()] obj[*(), foo=3] # Equivalent to obj[(), foo=3] obj[*(x,)] # Equivalent to obj[(x,)] obj[*(x,),] # Equivalent to obj[(x,)]
Beachten Sie insbesondere Fall 3: Sequenz-Unpacking eines einzelnen Elements verhält sich nicht so, als ob nur ein einzelnes Argument übergeben wurde. Ein ähnlicher Fall ist das folgende Beispiel
obj[1, *(), foo=5] # Equivalent to obj[(1,), foo=5] # calls type(obj).__getitem__(obj, (1,), foo=5)
Wie wir jedoch bereits gesehen haben, wird aus Kompatibilitätsgründen ein einzelner Index unverändert übergeben
obj[1, foo=5] # calls type(obj).__getitem__(obj, 1, foo=5)
Mit anderen Worten, ein einzelner Positionsindex wird „wie er ist“ übergeben, nur wenn kein Sequenz-Unpacking vorhanden ist. Wenn Sequenz-Unpacking vorhanden ist, wird der Index zu einem Tupel, unabhängig von der resultierenden Anzahl von Elementen im Index nach dem Unpacking.
- Dict-Unpacking ist erlaubt
items = {'spam': 1, 'eggs': 2} obj[index, **items] # equivalent to obj[index, spam=1, eggs=2]
Die folgende Notation-Äquivalenz sollte eingehalten werden
obj[**{}] # Equivalent to obj[()] obj[3, **{}] # Equivalent to obj[3]
- Nur-Schlüsselwort-Subscripts sind erlaubt. Der Positionsindex ist das leere Tupel
obj[spam=1, eggs=2] # calls type(obj).__getitem__(obj, (), spam=1, eggs=2) obj[spam=1, eggs=2] = 5 # calls type(obj).__setitem__(obj, (), 5, spam=1, eggs=2) del obj[spam=1, eggs=2] # calls type(obj).__delitem__(obj, (), spam=1, eggs=2)
Die Wahl des leeren Tupels als Sentinel wurde diskutiert. Details sind im Abschnitt „Abgelehnte Ideen“ enthalten.
- Schlüsselwortargumente müssen Slice-Syntax erlauben
obj[3:4, spam=1:4, eggs=2] # calls type(obj).__getitem__(obj, slice(3, 4, None), spam=slice(1, 4, None), eggs=2)
Dies kann die Möglichkeit eröffnen, die gleiche Syntax für allgemeine Funktionsaufrufe zu akzeptieren, aber dies ist nicht Teil dieser Empfehlung.
- Schlüsselwortargumente erlauben Standardwerte
# Given type(obj).__getitem__(obj, index, spam=True, eggs=2) obj[3] # Valid. index = 3, spam = True, eggs = 2 obj[3, spam=False] # Valid. index = 3, spam = False, eggs = 2 obj[spam=False] # Valid. index = (), spam = False, eggs = 2 obj[] # Invalid.
- Die oben angegebenen Semantiken müssen auf
__class__getitem__erweitert werden: Seit PEP 560 werden Typ-Hints so verteilt, dass fürx[y], wenn keine__getitem__-Methode gefunden wird undxein Typ(Klassen)-Objekt ist undxeine Klassenmethode__class_getitem__hat, diese Methode aufgerufen wird. Die gleichen Änderungen sollten auch auf diese Methode angewendet werden, sodass eine Schreibweise wielist[T=int]akzeptiert werden kann.
Indizierungsverhalten in Standardklassen (dict, list, etc.)
Keine der in dieser PEP vorgeschlagenen Änderungen wird das Verhalten der aktuellen Kernklassen, die Indizierung verwenden, ändern. Das Hinzufügen von Schlüsselwörtern zur Indexoperation für benutzerdefinierte Klassen ist nicht dasselbe wie die Modifizierung z. B. des Standard-dict-Typs zur Handhabung von Schlüsselwortargumenten. Tatsächlich werden dict (sowie list und andere stdlib-Klassen mit Indizierungssemantik) unverändert bleiben und weiterhin keine Schlüsselwortargumente akzeptieren. Mit anderen Worten, wenn d ein dict ist, löst die Anweisung d[1, a=2] einen TypeError aus, da ihre Implementierung die Verwendung von Schlüsselwortargumenten nicht unterstützt. Das Gleiche gilt für alle anderen Klassen (list, dict, etc.).
Sonderfälle und Tücken
Mit der Einführung der neuen Notation müssen einige Sonderfälle analysiert werden.
- Technisch gesehen, wenn eine Klasse ihre Getter wie folgt definiert
def __getitem__(self, index):
dann könnte der Aufrufer dies über Schlüsselwortsyntax aufrufen, wie diese beiden Fälle
obj[3, index=4] obj[index=1]
Das resultierende Verhalten wäre automatisch ein Fehler, da es so wäre, als würde versucht, die Methode mit zwei Werten für das
index-Argument aufzurufen, und einTypeErrorwürde ausgelöst. Im ersten Fall wäre derindex3, im zweiten Fall wäre es das leere Tupel().Beachten Sie, dass dieses Verhalten für alle derzeit vorhandenen Klassen gilt, die auf Indizierung angewiesen sind, was bedeutet, dass das neue Verhalten in dieser Hinsicht keine Rückwärtskompatibilitätsprobleme verursachen kann.
Klassen, die dieses Verhalten explizit betonen möchten, können ihre Parameter als nur-positional definieren
def __getitem__(self, index, /):
- ein ähnlicher Fall tritt bei der Setter-Notation auf
# Given type(obj).__setitem__(obj, index, value): obj[1, value=3] = 5
Dies stellt kein Problem dar, da der Wert automatisch übergeben wird und der Python-Interpreter
TypeError: got multiple values for keyword argument 'value'auslöst - Wenn die Subscript-Dunders so deklariert sind, dass sie Positions-oder-Schlüsselwortparameter verwenden, kann es zu überraschenden Fällen kommen, wenn Argumente an die Methode übergeben werden. Bei der Signatur
def __getitem__(self, index, direction='north')
wenn der Aufrufer dies verwendet
obj[0, 'south']
wird er wahrscheinlich vom Methodenaufruf überrascht sein
# expected type(obj).__getitem__(obj, 0, direction='south') # but actually get: type(obj).__getitem__(obj, (0, 'south'), direction='north')
Lösung: Beste Praxis legt nahe, dass Schlüsselwort-Subscripts, wenn möglich, als nur-Schlüsselwort gekennzeichnet werden sollten
def __getitem__(self, index, *, direction='north')
Der Interpreter muss diese Regel nicht erzwingen, da es Szenarien geben kann, in denen dies das gewünschte Verhalten ist. Linter können jedoch darauf hinweisen, wenn Subscript-Methoden das Schlüsselwort-Nur-Flag nicht verwenden.
- Wie wir gesehen haben, wird ein einzelner Wert, gefolgt von einem Schlüsselwortargument, nicht in ein Tupel umgewandelt, d. h.:
d[1, a=3]wird als__getitem__(d, 1, a=3)behandelt, NICHT als__getitem__(d, (1,), a=3). Es wäre extrem verwirrend, wenn das Hinzufügen von Schlüsselwortargumenten den Typ des übergebenen Index ändern würde. Mit anderen Worten, das Hinzufügen eines Schlüsselworts zu einem einwertigen Subscript ändert es nicht in ein Tupel. Für Fälle, in denen ein tatsächliches Tupel übergeben werden muss, muss eine korrekte Syntax verwendet werdenobj[(1,), a=3] # calls type(obj).__getitem__(obj, (1,), a=3)
In diesem Fall übergibt der Aufruf ein einzelnes Element (das unverändert übergeben wird, wie in der obigen Regel), nur dass das einzelne Element zufällig ein Tupel ist.
Beachten Sie, dass dieses Verhalten lediglich die Tatsache aufzeigt, dass die Notation
obj[1,]eine Abkürzung fürobj[(1,)]ist (und auchobj[1]eine Abkürzung fürobj[(1)]ist, mit dem erwarteten Verhalten). Wenn Schlüsselwörter vorhanden sind, ist die Regel, dass diese äußerste Klammerpaar weggelassen werden kann, nicht mehr gültigobj[1] # calls type(obj).__getitem__(obj, 1) obj[1, a=3] # calls type(obj).__getitem__(obj, 1, a=3) obj[1,] # calls type(obj).__getitem__(obj, (1,)) obj[(1,), a=3] # calls type(obj).__getitem__(obj, (1,), a=3)
Dies ist besonders relevant, wenn zwei Einträge übergeben werden
obj[1, 2] # calls type(obj).__getitem__(obj, (1, 2)) obj[(1, 2)] # same as above obj[1, 2, a=3] # calls type(obj).__getitem__(obj, (1, 2), a=3) obj[(1, 2), a=3] # calls type(obj).__getitem__(obj, (1, 2), a=3)
Und insbesondere, wenn das Tupel als Variable extrahiert wird
t = (1, 2) obj[t] # calls type(obj).__getitem__(obj, (1, 2)) obj[t, a=3] # calls type(obj).__getitem__(obj, (1, 2), a=3)
Warum? Weil im Fall
obj[1, 2, a=3]zwei Elemente übergeben werden (die dann als Tupel gepackt und als Index übergeben werden). Im Fallobj[(1, 2), a=3]wird ein einzelnes Element übergeben (das unverändert übergeben wird), das zufällig ein Tupel ist. Das Endergebnis ist, dass sie gleich sind.
C-Schnittstelle
Die Auflösung der Indizierungsoperation erfolgt über einen Aufruf der folgenden Funktionen
PyObject_GetItem(PyObject *o, PyObject *key)für die Get-OperationPyObject_SetItem(PyObject *o, PyObject *key, PyObject *value)für die Set-OperationPyObject_DelItem(PyObject *o, PyObject *key)für die Del-Operation
Diese Funktionen werden im gesamten Python-Interpreter ausgiebig verwendet und sind auch Teil der öffentlichen C-API, wie sie von Include/abstract.h exportiert werden. Es ist klar, dass die Signatur dieser Funktion nicht geändert werden kann und unterschiedliche C-Level-Funktionen implementiert werden müssen, um den erweiterten Aufruf zu unterstützen. Wir schlagen vor
PyObject_GetItemWithKeywords(PyObject *o, PyObject *key, PyObject *kwargs)PyObject_SetItemWithKeywords(PyObject *o, PyObject *key, PyObject *value, PyObject *kwargs)PyObject_GetItemWithKeywords(PyObject *o, PyObject *key, PyObject *kwargs)
Neue Opcodes werden für den erweiterten Aufruf benötigt. Derzeit verwendet die Implementierung BINARY_SUBSCR, STORE_SUBSCR und DELETE_SUBSCR, um die alten Funktionen aufzurufen. Wir schlagen BINARY_SUBSCR_KW, STORE_SUBSCR_KW und DELETE_SUBSCR_KW für die neuen Operationen vor. Der Compiler muss diese neuen Opcodes generieren. Die alten C-Implementierungen rufen die erweiterten Methoden auf und übergeben NULL als kwargs.
Schließlich müssen die folgenden neuen Slots zur Struktur PyMappingMethods hinzugefügt werden
mp_subscript_kwmp_ass_subscript_kw
Diese Slots haben die entsprechende Signatur, um das Wörterbuch-Objekt zu handhaben, das die Schlüsselwörter enthält.
Empfehlungen „Wie man lehrt“
Eine Anfrage, die während der Feedbackgespräche aufkam, war, eine mögliche Erzählung für das Lehren der Funktion zu detaillieren, z. B. für Studenten, Datenwissenschaftler und ähnliche Zielgruppen. Dieser Abschnitt adressiert diesen Bedarf.
Wir werden die Indizierung nur aus der Perspektive der Nutzung beschreiben, nicht der Implementierung, da dies der Aspekt ist, dem die oben genannten Zielgruppen wahrscheinlich begegnen werden. Nur ein Teil der Benutzer muss eigene Dunder-Funktionen implementieren und kann als fortgeschrittene Nutzung betrachtet werden. Eine angemessene Erklärung könnte lauten:
Die Indizierungsoperation wird im Allgemeinen verwendet, um mit Hilfe eines Index auf eine Teilmenge eines größeren Datensatzes zu verweisen. In den gängigen Fällen besteht der Index aus einer oder mehreren Zahlen, Zeichenketten, Slices usw.Einige Typen können Indizierung nicht nur mit dem Index, sondern auch mit benannten Werten zulassen. Diese benannten Werte werden zwischen eckigen Klammern angegeben, wobei die gleiche Syntax wie für Funktionsaufruf-Schlüsselwortargumente verwendet wird. Die Bedeutung der Namen und ihre Verwendung finden sich in der Dokumentation des Typs, da sie von Typ zu Typ variieren.
Der Lehrer wird nun einige praktische Beispiele aus der realen Welt zeigen und die Semantik der Funktion in der gezeigten Bibliothek erklären. Zum Zeitpunkt der Erstellung existieren diese Beispiele offensichtlich nicht, aber die wahrscheinlichsten Bibliotheken, die die Funktion implementieren werden, sind pandas und numpy, möglicherweise als Methode, um Spalten nach Namen zu referenzieren.
Referenzimplementierung
Eine Referenzimplementierung wird derzeit hier entwickelt [6].
Workarounds
Jede PEP, die die Python-Sprache ändert, sollte „klar erklären, warum die bestehende Sprachspezifikation nicht ausreicht, um das Problem zu lösen, das die PEP löst“.
Einige grobe Entsprechungen zur vorgeschlagenen Erweiterung, die wir Workarounds nennen, sind bereits möglich. Die Workarounds bieten eine Alternative zur Aktivierung der neuen Syntax und überlassen die Semantik der Definition an anderer Stelle.
Diese Workarounds folgen. In ihnen sind die Helfer H und P nicht als universell gedacht. Zum Beispiel könnte ein Modul oder Paket die Verwendung seiner eigenen Helfer erfordern.
- Benutzerdefinierte Klassen können
getitemunddelitemMethoden erhalten, die Werte aus einem Container abrufen bzw. löschen>>> val = x.getitem(1, 2, a=3, b=4) >>> x.delitem(1, 2, a=3, b=4)
Das Gleiche kann nicht für
setitemgetan werden. Es ist keine gültige Syntax>>> x.setitem(1, 2, a=3, b=4) = val SyntaxError: can't assign to function call
- Eine Hilfsklasse, hier
Hgenannt, kann verwendet werden, um die Rollen von Container und Parameter zu vertauschen. Mit anderen Worten, wir verwendenH(1, 2, a=3, b=4)[x]
als Ersatz für
x[1, 2, a=3, b=4]
Diese Methode funktioniert für
getitem,delitemund auch fürsetitem. Dies liegt daran, dass>>> H(1, 2, a=3, b=4)[x] = val
gültige Syntax ist, der die entsprechende Semantik gegeben werden kann.
- Eine Hilfsfunktion, hier
Pgenannt, kann verwendet werden, um die Argumente in einem einzelnen Objekt zu speichern. Zum Beispiel>>> x[P(1, 2, a=3, b=4)] = val
gültige Syntax ist und ihr die entsprechende Semantik gegeben werden kann.
- Die
lo:hi:step-Syntax für Slices ist manchmal sehr nützlich. Diese Syntax ist in den Workarounds nicht direkt verfügbar. Allerdingss[lo:hi:step]
bietet einen Workaround, der überall verfügbar ist, wo
class S: def __getitem__(self, key): return key s = S()
das Hilfsobjekt
sdefiniert.
Abgelehnte Ideen
Frühere PEP 472-Lösungen
PEP 472 präsentiert eine gute Anzahl von Ideen, die nun alle als abgelehnt gelten. Eine persönliche E-Mail von D’Aprano an den Autor besagte ausdrücklich
Ich habe nun die PEP 472 vollständig gelesen und fürchte, ich kann keine der derzeit in der PEP enthaltenen Strategien unterstützen.
Wir stimmen zu, dass diese Optionen aus den einen oder anderen Gründen den aktuell vorgestellten unterlegen sind.
Um dieses Dokument kompakt zu halten, werden wir hier nicht die Einwände gegen alle in PEP 472 vorgestellten Optionen darstellen. Es genügt zu sagen, dass sie diskutiert wurden und jede vorgeschlagene Alternative einen oder wenige Dealbreaker hatte.
Hinzufügen neuer Dunders
Es wurde vorgeschlagen, neue Dunders __(get|set|del)item_ex__ einzuführen, die über die __(get|set|del)item__-Triade aufgerufen werden, falls sie vorhanden sind.
Die Begründung für diese Wahl ist, die Intuition für die Unterstützung von Schlüsselwortargumenten für eckige Klammern offensichtlicher und im Einklang mit dem Funktionsverhalten zu machen. Angesichts
def __getitem_ex__(self, x, y): ...
All diese funktionieren einfach und produzieren mühelos das gleiche Ergebnis
obj[1, 2]
obj[1, y=2]
obj[y=2, x=1]
Mit anderen Worten, diese Lösung würde das Verhalten von __getitem__ an die traditionelle Funktionssignatur anpassen. Da wir jedoch __getitem__ nicht ändern und die Abwärtskompatibilität brechen können, hätten wir eine erweiterte Version, die bevorzugt verwendet wird.
Die Probleme mit diesem Ansatz wurden wie folgt festgestellt:
- Es verlangsamt die Indizierung. Bei jedem Zugriff auf einen Index wird dieses neue Dunder-Attribut in der Klasse untersucht. Wenn es nicht vorhanden ist, wird die Standardfunktion zur Schlüsselübersetzung ausgeführt. Es wurden verschiedene Ideen zur Bewältigung dieses Problems vorgeschlagen, vom reinen Umwickeln der Methode zum Zeitpunkt der Klasseninstanziierung bis hin zum Hinzufügen eines Bitflags, das die Verfügbarkeit dieser Methoden signalisiert. Unabhängig von der Lösung wäre das neue Dunder nur dann wirksam, wenn es bei der Klassenerstellung hinzugefügt wird, nicht aber, wenn es später hinzugefügt wird. Dies wäre ungewöhnlich und würde das Monkeypatching der Methoden aus welchen Gründen auch immer verbieten (und unerwartet funktionieren).
- Es erhöht die Komplexität des Mechanismus.
- Erfordert eine lange und schmerzhafte Übergangszeit, in der Bibliotheken irgendwie beide Aufrufkonventionen unterstützen müssen, da höchstwahrscheinlich die erweiterten Methoden bei Übereinstimmung der richtigen Bedingungen in den Argumenten auf die traditionellen delegieren, oder einige Klassen die traditionellen Dunder und andere die erweiterten Dunder unterstützen werden. Dies wird zwar den aufrufenden Code nicht beeinträchtigen, aber die Entwicklung.
- Es könnte potenziell zu gemischten Situationen führen, in denen die erweiterte Version für den Getter definiert ist, aber nicht für den Setter.
- In der Signatur von
__setitem_ex__müsste `value` zum ersten Element gemacht werden, da der Index eine beliebige Länge hat, abhängig von den angegebenen Indizes. Dies würde ungeschickt aussehen, da die visuelle Notation nicht mit der Signatur übereinstimmt.obj[1, 2] = 3 # calls type(obj).__setitem_ex__(obj, 3, 1, 2)
- Die Lösung beruht auf der Annahme, dass alle Schlüsselwortindizes notwendigerweise Positionsindizes zugeordnet sind oder einen Namen haben müssen. Diese Annahme könnte falsch sein: xarray, das wichtigste Python-Paket für numpy-Arrays mit benannten Dimensionen, unterstützt die Indizierung durch zusätzliche Dimensionen (sogenannte „non-dimension coordinates“), die nicht direkt den Dimensionen des zugrundeliegenden numpy-Arrays entsprechen, und diese haben keine Position, zu der sie passen würden. Mit anderen Worten, anonyme Indizes sind ein plausibler Anwendungsfall, den diese Lösung eliminieren würde, obwohl man argumentieren könnte, dass die Verwendung von
*argsdieses Problem lösen würde.
Hinzufügen einer Adapterfunktion
Ähnlich wie oben, in dem Sinne, dass eine Vorfunktion aufgerufen würde, um die „neue Stil“-Indizierung in eine „alte Stil“-Indizierung umzuwandeln, die dann übergeben wird. Hat Probleme, die den oben genannten ähneln.
Erstellen eines neuen „kwslice“-Objekts
Dieser Vorschlag wurde bereits in „New arguments contents“ P4 in PEP 472 untersucht.
obj[a, b:c, x=1]
# calls type(obj).__getitem__(obj, a, slice(b, c), key(x=1))
Diese Lösung erfordert, dass jeder, der Schlüsselwortargumente benötigt, das Tupel und/oder das Schlüsselobjekt manuell parst, um sie zu extrahieren. Dies ist mühsam und führt dazu, dass die Get/Set/Del-Funktion immer beliebige Schlüsselwortargumente akzeptiert, ob sie sinnvoll sind oder nicht. Wir möchten, dass der Entwickler angeben kann, welche Argumente sinnvoll sind und welche nicht.
Verwendung eines einzelnen Bits zur Verhaltensänderung
Ein spezielles Klassen-Dunder-Flag
__keyfn__ = True
würde die Signatur von __get|set|delitem__ in eine „funktionsähnliche“ Dispatch ändern, was bedeutet, dass dies
>>> d[1, 2, z=3]
zu einem Aufruf von führen würde
>>> type(obj).__getitem__(obj, 1, 2, z=3)
# instead of type(obj).__getitem__(obj, (1, 2), z=3)
Diese Option wurde abgelehnt, da es seltsam erscheint, dass die Signatur einer Methode von einem bestimmten Wert eines anderen Dunders abhängt. Dies wäre sowohl für statische Typenprüfer als auch für Menschen verwirrend: Ein statischer Typenprüfer müsste einen Sonderfall dafür hart kodieren, da es wirklich nichts anderes in Python gibt, bei dem die Signatur eines Dunders vom Wert eines anderen Dunders abhängt. Ein Mensch, der einen __getitem__ Dunder implementieren muss, müsste in der Klasse (oder in einer ihrer Unterklassen) nach einem __keyfn__ suchen, bevor der Dunder geschrieben werden kann. Darüber hinaus würde das Hinzufügen von Basisklassen, bei denen das Flag __keyfn__ gesetzt ist, die Signatur der aktuellen Methoden brechen. Dies wäre noch problematischer, wenn das Flag zur Laufzeit geändert wird oder wenn das Flag durch Aufruf einer Funktion generiert wird, die zufällig True oder etwas anderes zurückgibt.
Zulassen leerer Indexnotation obj[]
Der aktuelle Vorschlag verhindert, dass obj[] eine gültige Notation ist. Ein Kommentator bemerkte jedoch
Wir habenTuple[int, int]als Tupel von zwei Ganzzahlen. Und wir habenTuple[int]als Tupel von einer Ganzzahl. Und gelegentlich müssen wir ein Tupel von *keinen* Werten schreiben, da dies der Typ von()ist. Aber wir sind derzeit gezwungen, dies alsTuple[()]zu schreiben. Wenn wirTuple[]erlauben würden, würde dieser seltsame Grenzfall entfallen.Also wäre ich wahrscheinlich damit einverstanden,
obj[]syntaktisch zuzulassen, solange der Diktatyp ihn ablehnen kann.
Dieser Vorschlag hat bereits festgelegt, dass im Fall, dass kein positionsbezogener Index angegeben wird, der übergebene Wert das leere Tupel sein muss. Das Zulassen der Notation eines leeren Index würde dazu führen, dass der Diktatyp ihn automatisch akzeptiert, um den Wert mit dem leeren Tupel als Schlüssel einzufügen oder darauf zu verweisen. Darüber hinaus kann eine Typnotationsweise wie Tuple[] leicht als Tuple ohne die Indizierungsnotation geschrieben werden.
Nachfolgende Diskussionen mit Brandt Bucher während der Implementierung haben jedoch ergeben, dass der Fall obj[] sich gut in eine natürliche Entwicklung für variadische Generika einfügen würde, was dem obigen Kommentar mehr Gewicht verleiht. Am Ende haben wir uns nach einer Diskussion zwischen D’Aprano, Bucher und dem Autor darauf geeinigt, die Notation obj[] vorerst als Syntaxfehler zu belassen und die Notation möglicherweise mit einer zusätzlichen PEP zu erweitern, um die Äquivalenz obj[] als obj[()] zu behandeln.
Sentinel-Wert für keinen gegebenen Positionsindex
Das Thema, welcher Wert als Index im Fall von übergeben werden soll
obj[k=3]
wurde erheblich debattiert.
Eine scheinbar rationale Wahl wäre, gar keinen Wert zu übergeben, indem man die Keyword-Only-Argument-Funktion nutzt, aber das funktioniert leider nicht gut mit dem __setitem__ Dunder, da immer ein positionelles Element für den Wert übergeben wird, und wir können den Index-Parameter nicht „überspringen“, es sei denn, wir führen ein sehr seltsames Verhalten ein, bei dem das erste Argument sich auf den Index bezieht, wenn es angegeben ist, und auf den Wert, wenn der Index nicht angegeben ist. Dies ist extrem täuschend und fehleranfällig.
Die obige Überlegung macht es unmöglich, einen Keyword-Only-Dunder zu haben, und wirft die Frage auf, welche Entität für die Indexposition übergeben werden soll, wenn kein Index übergeben wird.
obj[k=3] = 5
# would call type(obj).__setitem__(obj, ???, 5, k=3)
Ein vorgeschlagener Hack wäre, den Benutzer angeben zu lassen, welche Entität verwendet werden soll, wenn kein Index angegeben wird, indem ein Standardwert für den index angegeben wird. Dies zwingt jedoch notwendigerweise, auch einen (nie zu verwendenden, da ein Wert aufgrund des Designs immer übergeben wird) Standardwert für den value anzugeben, da wir keine Nicht-Standardargumente nach Standardargumenten haben können.
def __setitem__(self, index=SENTINEL, value=NEVERUSED, *, k)
was hässlich, redundant und verwirrend erscheint. Wir müssen daher akzeptieren, dass eine Form von Sentinel-Index von der Python-Implementierung übergeben werden muss, wenn die Notation obj[k=3] verwendet wird. Dies bedeutet auch, dass Standardargumente für diese Parameter einfach nie verwendet werden (aber das ist bereits bei der aktuellen Implementierung der Fall, also keine Änderung dort).
Zusätzlich möchten einige Klassen **kwargs anstelle eines Keyword-Only-Arguments verwenden, was bedeutet, dass eine Definition wie
def __setitem__(self, index, value, **kwargs):
und ein Benutzer, der ein Schlüsselwort value übergeben möchte
x[value=1] = 0
erwartet einen Aufruf wie
type(obj).__setitem__(obj, SENTINEL, 0, **{"value": 1})
stattdessen versehentlich vom benannten value erfasst wird, was zu einem duplicate value error führt. Der Benutzer sollte sich nicht um die tatsächlichen lokalen Namen dieser beiden Argumente sorgen müssen, wenn sie für alle praktischen Zwecke positionsbezogen sind. Leider stellt die Verwendung von positionsbezogenen Werten sicher, dass dies nicht geschieht, löst aber immer noch nicht die Notwendigkeit, sowohl index als auch value zu übergeben, auch wenn der Index nicht bereitgestellt wird. Der Punkt ist, dass der Benutzer nicht daran gehindert werden sollte, Schlüsselwortargumente zu verwenden, um auf eine Spalte index, value (oder self) zu verweisen, nur weil der Klassenimplementierer diese Namen in der Parameterliste verwendet.
Darüber hinaus verlangen wir, dass die drei Dunder auf die gleiche Weise funktionieren: Es wäre äußerst unbequem, wenn nur __setitem__ diesen Sentinel erhalten würde und __get|delitem__ nicht, da sie mit einer Signatur davonkommen können, die keine Indexspezifikation erlaubt, und somit einen benutzerdefinierten Standardindex ermöglicht.
Welche Wahl des Sentinels auch immer getroffen wird, sie wird dazu führen, dass die folgenden Fälle degenerieren und somit im Dunder nicht mehr zu unterscheiden sind.
obj[k=3]
obj[SENTINEL, k=3]
Die Frage verschiebt sich nun darauf, welche Entität den Sentinel darstellen soll: Die Optionen waren
- Leeres Tupel
- Keine
- NichtImplementiert
- Ein neues Sentinel-Objekt (z. B. `NoIndex`)
Für Option 1 wird der Aufruf lauten
type(obj).__getitem__(obj, (), k=3)
wodurch obj[k=3] und obj[(), k=3] degenerieren und nicht mehr unterscheidbar sind.
Diese Option klingt ansprechend, weil
- Die NumPy-Community wurde befragt [5], und der allgemeine Konsens der Antworten war, dass das leere Tupel als passend empfunden wurde.
- Sie zeigt eine Parallele zum Verhalten von
*argsin einer Funktion, wenn keine Positionsargumente gegeben sind.>>> def foo(*args, **kwargs): ... print(args, kwargs) ... >>> foo(k=3) () {'k': 3}
Obwohl wir die folgende Asymmetrie im Verhalten im Vergleich zu Funktionen akzeptieren, wenn ein einzelner Wert übergeben wird, ist dieses Schiff bereits abgefahren.
>>> foo(5, k=3) (5,) {'k': 3} # for indexing, a plain 5, not a 1-tuple is passed
Für Option 2, die Verwendung von None, wurde beanstandet, dass NumPy sie verwendet, um das Einfügen einer neuen Achse/Dimension anzuzeigen (es gibt auch einen np.newaxis Alias).
arr = np.array(5)
arr.ndim == 0
arr[None].ndim == arr[None,].ndim == 1
Das ist zwar kein unüberwindbares Problem, aber es wird sich sicherlich auf NumPy auswirken.
Die einzigen Probleme bei beiden obigen Optionen sind, dass sowohl das leere Tupel als auch `None` potenzielle legitime Indizes sind und es von Wert sein könnte, die beiden degenerierten Fälle unterscheiden zu können.
Eine alternative Strategie (Option 3) wäre daher, eine existierende Entität zu verwenden, die unwahrscheinlich als gültiger Index verwendet wird. Eine Option könnte die aktuelle eingebaute Konstante NotImplemented sein, die derzeit von Operatormethoden zurückgegeben wird, um zu melden, dass sie eine bestimmte Operation nicht implementieren und eine andere Strategie versucht werden sollte (z. B. das andere Objekt abfragen). Leider rufen ihr Name und ihre traditionelle Verwendung eine nicht verfügbare Funktion auf, anstatt die Tatsache, dass etwas nicht vom Benutzer übergeben wurde.
Dies lässt uns mit Option 4: eine neue eingebaute Konstante. Diese Konstante muss unhashable sein (damit sie nie ein gültiger Schlüssel ist) und einen klaren Namen haben, der ihren Kontext deutlich macht: NoIndex. Dies würde alle obigen Probleme lösen, aber die Frage ist: Lohnt es sich?
Aus einer schnellen Nachfrage scheint es, dass die meisten Leute auf python-ideas es nicht für entscheidend halten und das leere Tupel eine akzeptable Option ist. Daher wird die resultierende Serie sein
obj[k=3]
# type(obj).__getitem__(obj, (), k=3). Empty tuple
obj[1, k=3]
# type(obj).__getitem__(obj, 1, k=3). Integer
obj[1, 2, k=3]
# type(obj).__getitem__(obj, (1, 2), k=3). Tuple
und die folgenden beiden Notationen werden degeneriert sein.
obj[(), k=3]
# type(obj).__getitem__(obj, (), k=3)
obj[k=3]
# type(obj).__getitem__(obj, (), k=3)
Gängige Einwände
- Verwenden Sie einfach einen Methodenaufruf.
Einer der Anwendungsfälle ist die Typisierung, bei der die Indizierung ausschließlich verwendet wird und Funktionsaufrufe außer Frage stehen. Darüber hinaus verarbeiten Funktionsaufrufe keine Slice-Notation, die in einigen Fällen für Arrays üblich ist.
Ein Problem ist, dass die Erstellung von Typ-Hinweisen in Python 3.9 auf integrierte Typen erweitert wurde, sodass Sie `Dict`, `List` usw. nicht mehr importieren müssen.
Ohne `kwargs` innerhalb von
[]könnten Sie dies nicht tun.Vector = dict[i=float, j=float]
Aber aus offensichtlichen Gründen ist die Aufrufsyntax mit integrierten Typen zur Erstellung benutzerdefinierter Typ-Hinweise keine Option.
dict(i=float, j=float) # would create a dictionary, not a type
Schließlich erlauben Funktionsaufrufe keine `setitem`-ähnliche Notation, wie in der Übersicht gezeigt: Operationen wie
f(1, x=3) = 5sind nicht erlaubt und stattdessen für Indexierungsoperationen zulässig.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0637.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT