PEP 570 – Python Positional-Only Parameter
- Autor:
- Larry Hastings <larry at hastings.org>, Pablo Galindo <pablogsal at python.org>, Mario Corchero <mariocj89 at gmail.com>, Eric N. Vander Weele <ericvw at gmail.com>
- BDFL-Delegate:
- Guido van Rossum <guido at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 20-Jan-2018
- Python-Version:
- 3.8
Zusammenfassung
Dieses PEP schlägt die Einführung einer neuen Syntax, /, zur Angabe von Positional-Only Parametern in Python-Funktionsdefinitionen vor.
Positional-Only Parameter haben keinen extern nutzbaren Namen. Wenn eine Funktion, die Positional-Only Parameter akzeptiert, aufgerufen wird, werden Positionsargumente ausschließlich anhand ihrer Reihenfolge diesen Parametern zugeordnet.
Beim Entwurf von APIs (Application Programming Interfaces) versuchen Bibliotheksautoren, eine korrekte und beabsichtigte Nutzung einer API sicherzustellen. Ohne die Möglichkeit, anzugeben, welche Parameter Positional-Only sind, müssen Bibliotheksautoren bei der Auswahl geeigneter Parameternamen vorsichtig sein. Diese Vorsicht ist selbst bei erforderlichen Parametern geboten oder wenn die Parameter keine externe semantische Bedeutung für Aufrufer der API haben.
In diesem PEP diskutieren wir
- Pythons Geschichte und aktuelle Semantik für Positional-Only Parameter
- die Probleme, die sich aus dem Fehlen ergeben
- wie diese Probleme ohne sprachintrinsische Unterstützung für Positional-Only Parameter gehandhabt werden
- die Vorteile von Positional-Only Parametern
Im Kontext der Motivation werden wir dann
- diskutieren, warum Positional-Only Parameter ein intrinsisches Merkmal der Sprache sein sollten
- die Syntax zur Kennzeichnung von Positional-Only Parametern vorschlagen
- darlegen, wie diese neue Funktion vermittelt werden kann
- abgelehnte Ideen detaillierter erläutern
Motivation
Verlauf der Semantik von Positional-Only Parametern in Python
Python unterstützte ursprünglich Positional-Only Parameter. Frühe Versionen der Sprache boten nicht die Möglichkeit, Funktionen mit nach Namen gebundenen Argumenten aufzurufen. Um Python 1.0 herum änderte sich die Parametersemantik zu Positions-oder-Schlüsselwort. Seitdem konnten Benutzer Argumente entweder positionell oder über den in der Funktionsdefinition angegebenen Schlüsselwortnamen an eine Funktion übergeben.
In aktuellen Versionen von Python akzeptieren viele CPython „eingebaute“ und Standardbibliotheksfunktionen nur Positional-Only Parameter. Die daraus resultierende Semantik kann leicht beobachtet werden, indem eine dieser Funktionen mit Schlüsselwortargumenten aufgerufen wird
>>> help(pow)
...
pow(x, y, z=None, /)
...
>>> pow(x=5, y=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pow() takes no keyword arguments
pow() drückt aus, dass seine Parameter über die / Markierung Positional-Only sind. Dies ist jedoch nur eine Dokumentationskonvention; Python-Entwickler können diese Syntax im Code nicht verwenden.
Es gibt Funktionen mit anderen interessanten Semantiken
range(), eine überladene Funktion, akzeptiert einen optionalen Parameter links von ihrem erforderlichen Parameter. [4]dict(), deren Mapping-/Iterator-Parameter optional und semantisch Positional-Only sein muss. Jeder extern sichtbare Name für diesen Parameter würde diesen Namen überdecken, der in den**kwargSchlüsselwort-Variadic-Parameter-Dict eingeht. [3]
Man kann diese Semantiken im Python-Code emulieren, indem man (*args, **kwargs) akzeptiert und die Argumente manuell parst. Dies führt jedoch zu einer Diskrepanz zwischen der Funktionsdefinition und dem, was die Funktion vertraglich akzeptiert. Die Funktionsdefinition stimmt nicht mit der Logik der Argumentenbehandlung überein.
Zusätzlich wird die /-Syntax über CPython hinaus zur Angabe ähnlicher Semantiken verwendet (d. h. [1] [2]); was darauf hinweist, dass diese Szenarien nicht auf CPython und die Standardbibliothek beschränkt sind.
Probleme ohne Positional-Only Parameter
Ohne Positional-Only Parameter gibt es Herausforderungen für Bibliotheksautoren und Benutzer von APIs. Die folgenden Unterabschnitte skizzieren die Probleme, auf die jede Entität stößt.
Herausforderungen für API-Benutzer
Benutzer könnten beim ersten Kontakt mit der Positional-Only Notation überrascht sein. Dies ist zu erwarten, da sie erst kürzlich dokumentiert wurde [13] und in Python-Code nicht verwendet werden kann. Aus diesen Gründen ist diese Notation derzeit ein Ausreißer, der nur in CPython-APIs vorkommt, die in C entwickelt wurden. Die Dokumentation der Notation und die Möglichkeit, sie in Python-Code zu verwenden, würde diese Diskrepanz beseitigen.
Darüber hinaus ist die aktuelle Dokumentation für Positional-Only Parameter inkonsistent
- Einige Funktionen kennzeichnen optionale Gruppen von Positional-Only Parametern, indem sie diese in verschachtelte eckige Klammern einschließen. [5]
- Einige Funktionen kennzeichnen optionale Gruppen von Positional-Only Parametern, indem sie mehrere Prototypen mit unterschiedlicher Anzahl von Parametern präsentieren. [6]
- Einige Funktionen verwenden *beide* oben genannten Ansätze. [4] [7]
Ein weiterer Punkt, den die aktuelle Dokumentation nicht unterscheidet, ist, ob eine Funktion Positional-Only Parameter akzeptiert. open() akzeptiert Schlüsselwortargumente; ord() tut dies jedoch nicht — es gibt keine Möglichkeit, dies allein durch Lesen der vorhandenen Dokumentation zu erkennen.
Vorteile von Positional-Only Parametern
Positional-Only Parameter geben Bibliotheksautoren mehr Kontrolle, um die beabsichtigte Nutzung einer API besser auszudrücken, und ermöglichen eine sichere, abwärtskompatible Weiterentwicklung der API. Außerdem macht es die Python-Sprache konsistenter mit bestehender Dokumentation und dem Verhalten verschiedener „eingebauter“ und Standardbibliotheksfunktionen.
Verbesserung der Sprachkonsistenz
Die Python-Sprache wäre konsistenter mit Positional-Only Parametern. Wenn das Konzept ein normales Merkmal von Python und nicht nur ein Merkmal von Erweiterungsmodulen wäre, würde dies die Verwirrung für Benutzer, die Funktionen mit Positional-Only Parametern antreffen, verringern. Einige wichtige Drittanbieter-Pakete verwenden die /-Notation bereits in ihren Funktionsdefinitionen [1] [2].
Die Lücke zwischen „eingebauten“ Funktionen, die Positional-Only Parameter angeben, und reinen Python-Implementierungen, denen die Positions-Syntax fehlt, zu schließen, würde die Konsistenz verbessern. Die /-Syntax ist bereits in der bestehenden Dokumentation vorhanden, wie z. B. wenn Builtins und Schnittstellen vom Argument Clinic generiert werden.
Ein weiterer wichtiger Aspekt ist PEP 399, der vorschreibt, dass reine Python-Versionen von Modulen in der Standardbibliothek die gleiche Schnittstelle und Semantik haben **müssen**, die auch die in C implementierten Beschleunigermodule haben. Wenn beispielsweise collections.defaultdict eine reine Python-Implementierung hätte, müsste es Positional-Only Parameter verwenden, um der Schnittstelle seines C-Gegenstücks zu entsprechen.
Begründung
Wir schlagen vor, Positional-Only Parameter als neue Syntax in die Python-Sprache einzuführen.
Die neue Syntax ermöglicht es Bibliotheksautoren, besser zu steuern, wie ihre API aufgerufen werden kann. Sie ermöglicht die Kennzeichnung, welche Parameter als Positional-Only aufgerufen werden müssen, und verhindert gleichzeitig, dass sie als Schlüsselwortargumente aufgerufen werden können.
Zuvor definierte (informell) PEP 457 die Syntax, jedoch mit einem viel vageren Geltungsbereich. Dieses PEP geht über den ursprünglichen Vorschlag hinaus, indem es die Syntax rechtfertigt und eine Implementierung für die /-Syntax in Funktionsdefinitionen bereitstellt.
Performance
Neben den oben genannten Vorteilen ist die Verarbeitung und Handhabung von Positional-Only Argumenten schneller. Dieser Leistungsvorteil kann in diesem Thread zur Konvertierung von Schlüsselwortargumenten in Positionsargumente demonstriert werden: [11]. Aufgrund dieser Beschleunigung gab es einen jüngsten Trend, Builtins von Schlüsselwortargumenten wegzubewegen: Kürzlich wurden abwärtsinkompatible Änderungen vorgenommen, um Schlüsselwortargumente für bool, float, list, int, tuple zu verbieten.
Wartbarkeit
Die Bereitstellung einer Möglichkeit, Positional-Only Parameter in Python anzugeben, erleichtert die Wartung reiner Python-Implementierungen von C-Modulen. Darüber hinaus haben Bibliotheksautoren, die Funktionen definieren, die Wahl, Positional-Only Parameter zu wählen, wenn sie feststellen, dass die Übergabe eines Schlüsselwortarguments keine zusätzliche Klarheit bietet.
Dies ist ein gut diskutiertes, wiederkehrendes Thema in den Python-Mailinglisten
- September 2018: Anders Hovmöller: [Python-ideas] Positional-only parameters
- Februar 2017: Victor Stinner: [Python-ideas] Positional-only parameters, Diskussion fortgesetzt im März
- Februar 2017: [9]
- März 2012: [8]
- Mai 2007: George Sakkis: [Python-ideas] Positional only arguments
- Mai 2006: Benji York: [Python-Dev] Positional-only Arguments
Logische Reihenfolge
Positional-Only Parameter haben auch den (kleinen) Vorteil, eine logische Reihenfolge bei der Aufrufung von Schnittstellen zu erzwingen, die sie nutzen. Zum Beispiel akzeptiert die range-Funktion alle ihre Parameter positionell und verbietet Formen wie
range(stop=5, start=0, step=2)
range(stop=5, step=2, start=0)
range(step=2, start=0, stop=5)
range(step=2, stop=5, start=0)
zum Preis der Verhinderung der Verwendung von Schlüsselwortargumenten für die (einzigartige) beabsichtigte Reihenfolge
range(start=0, stop=5, step=2)
Kompatibilität für reine Python- und C-Module
Eine weitere kritische Motivation für Positional-Only Parameter ist PEP 399: Pure Python/C Accelerator Module Compatibility Requirements. Dieses PEP besagt, dass
Dieses PEP verlangt, dass der C-Code in diesen Fällen die Testsuite für den reinen Python-Code besteht, um so weit wie vernünftigerweise möglich ein Drop-in-Ersatz zu sein.
Wenn der C-Code unter Verwendung der bestehenden Fähigkeiten zur Implementierung von Positional-Only Parametern mithilfe des Argument Clinic und verwandter Maschinerie implementiert wird, ist es für das reine Python-Gegenstück nicht möglich, die bereitgestellte Schnittstelle und Anforderungen zu erfüllen. Dies schafft eine Diskrepanz zwischen den Schnittstellen einiger Funktionen und Klassen in der CPython-Standardbibliothek und anderen Python-Implementierungen. Zum Beispiel
$ python3 # CPython 3.7.2
>>> import binascii; binascii.crc32(data=b'data')
TypeError: crc32() takes no keyword arguments
$ pypy3 # PyPy 6.0.0
>>>> import binascii; binascii.crc32(data=b'data')
2918445923
Andere Python-Implementierungen können die CPython-APIs manuell reproduzieren, aber dies widerspricht dem Geist von PEP 399, um Aufwandswiederholungen zu vermeiden, indem vorgeschrieben wird, dass alle Module, die der Python-Standardbibliothek hinzugefügt werden, **eine** reine Python-Implementierung mit der gleichen Schnittstelle und Semantik haben müssen.
Konsistenz in Unterklassen
Ein weiteres Szenario, in dem Positional-Only Parameter Vorteile bieten, tritt auf, wenn eine Unterklasse eine Methode der Basisklasse überschreibt und den Namen von Parametern ändert, die als positionell gedacht sind.
class Base:
def meth(self, arg: int) -> str:
...
class Sub(Base):
def meth(self, other_arg: int) -> str:
...
def func(x: Base):
x.meth(arg=12)
func(Sub()) # Runtime error
Diese Situation könnte als Liskov-Verletzung betrachtet werden – die Unterklasse kann nicht in einem Kontext verwendet werden, in dem eine Instanz der Basisklasse erwartet wird. Das Umbenennen von Argumenten beim Überladen von Methoden kann passieren, wenn die Unterklasse Gründe hat, eine andere Wahl für den Parameternamen zu treffen, die für den spezifischen Bereich der Unterklasse besser geeignet ist (z. B. beim Unterklassen von Mapping zur Implementierung eines DNS-Lookup-Caches, die abgeleitete Klasse möchte möglicherweise nicht die generischen Argumentnamen „key“ und „value“ verwenden, sondern eher „host“ und „address“). Das Vorhandensein dieser Funktionsdefinition mit Positional-Only Parametern kann dieses Problem vermeiden, da Benutzer die Schnittstelle nicht über Schlüsselwortargumente aufrufen können. Im Allgemeinen beinhaltet das Design für Unterklassen das Antizipieren von Code, der noch nicht geschrieben wurde und über den der Autor keine Kontrolle hat. Maßnahmen, die die Weiterentwicklung von Schnittstellen auf abwärtskompatible Weise erleichtern können, wären für Bibliotheksautoren nützlich.
Optimierungen
Ein letztes Argument für Positional-Only Parameter ist, dass sie einige neue Optimierungen ermöglichen, wie die bereits im Argument Clinic vorhandenen, da die Parameter in strenger Reihenfolge übergeben werden sollen. Zum Beispiel wurde die interne METH_FASTCALL-Aufrufkonvention von CPython kürzlich für Funktionen mit Positional-Only Parametern spezialisiert, um die Kosten für die Handhabung leerer Schlüsselwörter zu eliminieren. Ähnliche Leistungsverbesserungen können beim Erstellen des Ausführungsrahmens von Python-Funktionen dank Positional-Only Parametern angewendet werden.
Spezifikation
Syntax und Semantik
Aus der „Zehntausend-Fuß-Perspektive“, wobei *args und **kwargs zur Veranschaulichung weggelassen werden, würde die Grammatik einer Funktionsdefinition so aussehen
def name(positional_or_keyword_parameters, *, keyword_only_parameters):
Aufbauend auf diesem Beispiel würde die neue Syntax für Funktionsdefinitionen so aussehen
def name(positional_only_parameters, /, positional_or_keyword_parameters,
*, keyword_only_parameters):
Das Folgende würde gelten
- Alle Parameter links von
/werden als Positional-Only behandelt. - Wenn
/in der Funktionsdefinition nicht angegeben ist, akzeptiert diese Funktion keine Positional-Only Argumente. - Die Logik rund um optionale Werte für Positional-Only Parameter bleibt dieselbe wie für Positions-oder-Schlüsselwortparameter.
- Sobald ein Positional-Only Parameter mit einem Standardwert angegeben ist, müssen die folgenden Positional-Only und Positions-oder-Schlüsselwortparameter ebenfalls Standardwerte haben.
- Positional-Only Parameter ohne Standardwerte sind **erforderliche** Positional-Only Parameter.
Daher wären die folgenden gültige Funktionsdefinitionen
def name(p1, p2, /, p_or_kw, *, kw):
def name(p1, p2=None, /, p_or_kw=None, *, kw):
def name(p1, p2=None, /, *, kw):
def name(p1, p2=None, /):
def name(p1, p2, /, p_or_kw):
def name(p1, p2, /):
Genau wie heute wären die folgenden gültige Funktionsdefinitionen
def name(p_or_kw, *, kw):
def name(*, kw):
Während die folgenden ungültig wären
def name(p1, p2=None, /, p_or_kw, *, kw):
def name(p1=None, p2, /, p_or_kw=None, *, kw):
def name(p1=None, p2, /):
Vollständige Grammatikspezifikation
Eine vereinfachte Ansicht der vorgeschlagenen Grammatikspezifikation ist
typedargslist:
tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' # and so on
varargslist:
vfpdef ['=' test] (',' vfpdef ['=' test])* ',' '/' [',' # and so on
Basierend auf der Referenzimplementierung in diesem PEP wäre die neue Regel für typedarglist
typedargslist: (tfpdef ['=' test] (',' tfpdef ['=' test])* ',' '/' [',' [tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']]]
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']] ] )| (
tfpdef ['=' test] (',' tfpdef ['=' test])* [',' [
'*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [',']]]
| '*' [tfpdef] (',' tfpdef ['=' test])* [',' ['**' tfpdef [',']]]
| '**' tfpdef [','])
und für varargslist wäre
varargslist: vfpdef ['=' test ](',' vfpdef ['=' test])* ',' '/' [',' [ (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']]]
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']) ]] | (vfpdef ['=' test] (',' vfpdef ['=' test])* [',' [
'*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']]]
| '*' [vfpdef] (',' vfpdef ['=' test])* [',' ['**' vfpdef [',']]]
| '**' vfpdef [',']
)
Semantische Randfälle
Das Folgende ist eine interessante Folgerung der Spezifikation. Betrachten Sie diese Funktionsdefinition
def foo(name, **kwds):
return 'name' in kwds
Es gibt keinen möglichen Aufruf, der sie dazu bringt, True zurückzugeben. Zum Beispiel
>>> foo(1, **{'name': 2})
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: foo() got multiple values for argument 'name'
>>>
Aber mit / können wir das unterstützen
def foo(name, /, **kwds):
return 'name' in kwds
Jetzt gibt der obige Aufruf True zurück.
Mit anderen Worten, die Namen von Positional-Only Parametern können ohne Mehrdeutigkeit in **kwds verwendet werden. (Als weiteres Beispiel profitiert dies den Signaturen von dict() und dict.update().)
Ursprung von „/“ als Trennzeichen
Die Verwendung von / als Trennzeichen wurde ursprünglich 2012 von Guido van Rossum vorgeschlagen [8]
Alternativer Vorschlag: wie wäre es mit einem „/“? Es ist so etwas wie das Gegenteil von „*“, das „Schlüsselwortargument“ bedeutet, und „/“ ist kein neues Zeichen.
Wie man das lehrt
Die Einführung einer dedizierten Syntax zur Kennzeichnung von Positional-Only Parametern ist eng mit den vorhandenen Schlüsselwort-Only Parametern vergleichbar. Das gemeinsame Vermitteln dieser Konzepte kann das Erlernen der möglichen Funktionsdefinitionen, die ein Benutzer antreffen oder entwerfen kann, **vereinfachen**.
Dieses PEP empfiehlt die Hinzufügung einer neuen Unterabschnitts in der Python-Dokumentation, im Abschnitt „Mehr über das Definieren von Funktionen“, wo die restlichen Argumenttypen diskutiert werden. Die folgenden Absätze dienen als Entwurf für diese Ergänzungen. Sie führen die Notation für sowohl Positional-Only als auch Keyword-Only Parameter ein. Es ist nicht beabsichtigt, erschöpfend zu sein, noch sollte es als die endgültige Version betrachtet werden, die in die Dokumentation aufgenommen werden soll.
Standardmäßig können Argumente entweder nach Position oder explizit nach Schlüsselwort an eine Python-Funktion übergeben werden. Aus Gründen der Lesbarkeit und Leistung ist es sinnvoll, die Art und Weise, wie Argumente übergeben werden können, einzuschränken, sodass ein Entwickler nur die Funktionsdefinition betrachten muss, um festzustellen, ob Elemente nach Position, nach Position oder Schlüsselwort oder nach Schlüsselwort übergeben werden.
Eine Funktionsdefinition kann so aussehen
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
----------- ---------- ----------
| | |
| Positional or keyword |
| - Keyword only
-- Positional only
wobei / und * optional sind. Wenn sie verwendet werden, zeigen diese Symbole die Art des Parameters anhand der Art und Weise an, wie Argumente an die Funktion übergeben werden können: Positional-Only, Positions-oder-Schlüsselwort und Schlüsselwort-Only. Schlüsselwortparameter werden auch als benannte Parameter bezeichnet.
Positions-oder-Schlüsselwortargumente
Wenn / und * in der Funktionsdefinition nicht vorhanden sind, können Argumente nach Position oder Schlüsselwort an eine Funktion übergeben werden.
Positions-Only Parameter
Wenn man dies genauer betrachtet, ist es möglich, bestimmte Parameter als **Positions-Only** zu kennzeichnen. Wenn **Positions-Only**, ist die Reihenfolge der Parameter wichtig, und die Parameter können nicht per Schlüsselwort übergeben werden. Positions-Only Parameter würden vor einem / (Schrägstrich) platziert werden. Die / wird verwendet, um die Positions-Only Parameter logisch von den restlichen Parametern zu trennen. Wenn kein / in der Funktionsdefinition vorhanden ist, gibt es keine Positional-Only Parameter.
Parameter, die der / folgen, können **Positions-oder-Schlüsselwort** oder **Schlüsselwort-Only** sein.
Keyword-Only Arguments
Um Parameter als **Schlüsselwort-Only** zu kennzeichnen, was bedeutet, dass die Parameter per Schlüsselwortargument übergeben werden müssen, platzieren Sie ein * in der Argumentenliste direkt vor dem ersten **Schlüsselwort-Only** Parameter.
Funktionsbeispiele
Betrachten Sie die folgenden Beispiel-Funktionsdefinitionen, wobei Sie genau auf die Markierungen / und * achten
>>> def standard_arg(arg):
... print(arg)
...
>>> def pos_only_arg(arg, /):
... print(arg)
...
>>> def kwd_only_arg(*, arg):
... print(arg)
...
>>> def combined_example(pos_only, /, standard, *, kwd_only):
... print(pos_only, standard, kwd_only)
Die erste Funktionsdefinition standard_arg, die vertrauteste Form, legt keine Einschränkungen für die Aufrufkonvention fest, und Argumente können nach Position oder Schlüsselwort übergeben werden.
>>> standard_arg(2)
2
>>> standard_arg(arg=2)
2
Die zweite Funktion pos_only_arg ist auf die ausschließliche Verwendung von Positions-Parametern beschränkt, da eine / in der Funktionsdefinition vorhanden ist.
>>> pos_only_arg(1)
1
>>> pos_only_arg(arg=1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: pos_only_arg() got an unexpected keyword argument 'arg'
Die dritte Funktion kwd_only_args erlaubt nur Schlüsselwortargumente, wie durch ein * in der Funktionsdefinition angezeigt.
>>> kwd_only_arg(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given
>>> kwd_only_arg(arg=3)
3
Und die letzte verwendet alle drei Aufrufkonventionen in derselben Funktionsdefinition.
>>> combined_example(1, 2, 3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() takes 2 positional arguments but 3 were given
>>> combined_example(1, 2, kwd_only=3)
1 2 3
>>> combined_example(1, standard=2, kwd_only=3)
1 2 3
>>> combined_example(pos_only=1, standard=2, kwd_only=3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: combined_example() got an unexpected keyword argument 'pos_only'
Zusammenfassung
Der Anwendungsfall bestimmt, welche Parameter in der Funktionsdefinition verwendet werden sollen.
def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
Als Leitfaden
- Verwenden Sie Positional-Only, wenn Namen keine Rolle spielen oder keine Bedeutung haben und es nur wenige Argumente gibt, die immer in der gleichen Reihenfolge übergeben werden.
- Verwenden Sie Keyword-Only, wenn Namen eine Bedeutung haben und die Funktionsdefinition durch explizite Namen verständlicher ist.
Referenzimplementierung
Eine anfängliche Implementierung, die die CPython-Testsuite besteht, steht zur Evaluierung zur Verfügung [10].
Die Vorteile dieser Implementierung sind die Geschwindigkeit bei der Handhabung von Positional-Only Parametern, die Konsistenz mit der Implementierung von Keyword-Only Parametern (PEP 3102) und eine einfachere Implementierung aller Werkzeuge und Module, die von dieser Änderung betroffen wären.
Abgelehnte Ideen
Nichts tun
Immer eine Option – der Status Quo. Während dies in Betracht gezogen wurde, sind die oben genannten Vorteile die Aufnahme in die Sprache wert.
Dekoratoren
Es wurde auf python-ideas [9] vorgeschlagen, einen Dekorator in Python für diese Funktion bereitzustellen.
Dieser Ansatz hat den Vorteil, die Funktionsdefinition nicht mit zusätzlicher Syntax zu „verschmutzen“. Wir haben uns jedoch entschieden, diese Idee abzulehnen, weil
- Sie führt eine Asymmetrie bei der Deklaration des Parameterverhaltens ein.
- Sie erschwert es statischen Analysewerkzeugen und Typcheckern, Positional-Only Parameter sicher zu identifizieren. Sie müssten die AST auf die Liste der Dekoratoren abfragen und den korrekten nach Namen oder mit zusätzlichen Heuristiken identifizieren, während Keyword-Only Parameter direkt in der AST exponiert sind. Damit Werkzeuge Positional-Only Parameter korrekt identifizieren können, müssten sie das Modul ausführen, um auf Metadaten zuzugreifen, die der Dekorator setzt.
- Fehler bei der Deklaration werden erst zur Laufzeit gemeldet.
- Es kann schwieriger sein, Positional-Only Parameter in langen Funktionsdefinitionen zu identifizieren, da der Benutzer sie zählen muss, um zu wissen, welcher der letzte ist, der vom Dekorator betroffen ist.
- Die
/-Syntax wurde bereits für C-Funktionen eingeführt. Diese Inkonsistenz wird die Implementierung von Werkzeugen und Modulen, die mit dieser Syntax umgehen, erschweren – einschließlich, aber nicht beschränkt auf, den Argument Clinic, das Inspect-Modul und dasast-Modul. - Die Dekorator-Implementierung würde wahrscheinlich Kosten bei der Laufzeit-Performance verursachen, insbesondere im Vergleich zur direkten Aufnahme der Unterstützung in den Interpreter.
Markierung pro Argument
Eine Markierung pro Argument ist eine weitere sprachintrinsische Option. Der Ansatz fügt jedem der Parameter ein Token hinzu, um anzuzeigen, dass sie Positional-Only sind, und erfordert, dass diese Parameter zusammen platziert werden. Beispiel
def (.arg1, .arg2, arg3):
Beachten Sie den Punkt (d. h. .) bei .arg1 und .arg2. Obwohl dieser Ansatz leichter zu lesen sein mag, wurde er abgelehnt, weil / als expliziter Marker mit * für Keyword-Only Argumente kongruent ist und weniger fehleranfällig ist.
Es ist erwähnenswert, dass einige Bibliotheken bereits einen führenden Unterstrich verwenden [12], um Parameter konventionell als Positional-Only anzuzeigen.
Verwendung von „__“ als Markierung pro Argument
Einige Bibliotheken und Anwendungen (wie mypy oder jinja) verwenden Namen, die mit einem doppelten Unterstrich (d. h. __) beginnen, als Konvention, um Positional-Only Parameter anzugeben. Wir haben die Idee, __ als neue Syntax einzuführen, aus folgenden Gründen abgelehnt:
- Es ist eine abwärtsinkompatible Änderung.
- Es ist nicht symmetrisch zu der Art und Weise, wie Keyword-Only Parameter derzeit deklariert werden.
- Die Abfrage der AST auf Positional-Only Parameter würde erfordern, die normalen Argumente zu prüfen und ihre Namen zu inspizieren, während Keyword-Only Parameter eine zugeordnete Eigenschaft haben (
FunctionDef.args.kwonlyargs). - Jeder Parameter müsste inspiziert werden, um zu wissen, wann Positional-Only Argumente enden.
- Die Markierung ist umständlicher und zwingt die Markierung jedes Positional-Only Parameters.
- Es kollidiert mit anderen Verwendungen des doppelten Unterstrich-Präfixes, wie der Namens-Manglung in Klassen.
Gruppieren von Positional-Only Parametern mit Klammern
Tuple Parameter Unpacking ist ein Python 2-Feature, das die Verwendung eines Tupels als Parameter in einer Funktionsdefinition ermöglicht. Es erlaubt, dass ein Sequenzargument automatisch entpackt wird. Ein Beispiel ist
def fxn(a, (b, c), d):
pass
Tuple Argument Unpacking wurde in Python 3 entfernt (PEP 3113). Es gab einen Vorschlag, diese Syntax zur Implementierung von Positional-Only Parametern wiederzuverwenden. Wir haben diese Syntax zur Kennzeichnung von Positional-Only Parametern aus mehreren Gründen abgelehnt:
- Die Syntax ist asymmetrisch in Bezug auf die Deklaration von Keyword-Only Parametern.
- Python 2 verwendet diese Syntax, was zu Verwirrung hinsichtlich des Verhaltens dieser Syntax führen könnte. Dies wäre für Benutzer überraschend, die Python 2-Codebasen portieren, die diese Funktion nutzten.
- Diese Syntax ist Tupel-Literalen sehr ähnlich. Dies kann zu zusätzlicher Verwirrung führen, da sie mit einer Tupel-Deklaration verwechselt werden kann.
Nach dem Trennzeichen-Vorschlag
Das Markieren von positionsbezogenen Parametern nach dem / war eine weitere in Betracht gezogene Idee. Wir konnten jedoch keinen Ansatz finden, der die Argumente nach dem Marker modifizieren würde. Andernfalls würden die Parameter vor dem Marker ebenfalls zwangsweise positionsbezogen-nur sein. Zum Beispiel
def (x, y, /, z):
Wenn wir definieren, dass / z als positionsbezogen-nur markiert, wäre es nicht möglich, x und y als Schlüsselwortargumente anzugeben. Ein Weg, diese Einschränkung zu umgehen, würde zu Verwirrung führen, da Schlüsselwortargumente derzeit nicht von positionsbezogenen Argumenten gefolgt werden können. Daher würde / sowohl die vorhergehenden als auch die folgenden Parameter als positionsbezogen-nur erzwingen.
Danksagungen
Ein Teil des Inhalts dieses PEP stammt aus Larry Hastings' PEP 457.
Die Verwendung von / als Trennzeichen zwischen positionsbezogenen-nur und positionsbezogenen-oder-Schlüsselwort-Parametern geht auf Guido van Rossum in einem Vorschlag aus dem Jahr 2012 zurück. [8]
Der Dank für die Diskussion über die Vereinfachung der Grammatik gebührt Braulio Valdivieso.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0570.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT