PEP 468 – Beibehaltung der Reihenfolge von **kwargs in einer Funktion.
- Autor:
- Eric Snow <ericsnowcurrently at gmail.com>
- Discussions-To:
- Python-Ideas Liste
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 05.Apr.2014
- Python-Version:
- 3.6
- Post-History:
- 05.Apr.2014, 08.Sep.2016
- Resolution:
- Python-Dev Nachricht
Zusammenfassung
Die **kwargs-Syntax in einer Funktionsdefinition zeigt an, dass der Interpreter alle Schlüsselwortargumente sammeln soll, die nicht zu anderen benannten Parametern gehören. Python behält jedoch nicht die Reihenfolge bei, in der diese gesammelten Schlüsselwortargumente an die Funktion übergeben wurden. In einigen Kontexten ist die Reihenfolge wichtig. Dieser PEP schreibt vor, dass die gesammelten Schlüsselwortargumente im Funktionskörper als geordnete Zuordnung exponiert werden.
Motivation
Die **kwargs-Syntax von Python in Funktionsdefinitionen bietet ein leistungsfähiges Mittel zur dynamischen Handhabung von Schlüsselwortargumenten. In einigen Anwendungen der Syntax (siehe Anwendungsfälle) erfordern die Semantiken, die auf die gesammelten Schlüsselwortargumente angewendet werden, dass die Reihenfolge beibehalten wird. Wenig überraschend ist dies ähnlich, wie OrderedDict zu dict verhält.
Derzeit müssen Sie die Reihenfolge manuell und getrennt vom eigentlichen Funktionsaufruf beibehalten. Dies beinhaltet das Erstellen einer geordneten Zuordnung, sei es ein OrderedDict oder ein iterierbares Tupel aus 2 Tupeln, das als einzelnes Argument an die Funktion übergeben wird. [1]
Mit der in diesem PEP beschriebenen Funktionalität wäre dieser Boilerplate-Code nicht mehr erforderlich.
Zum Vergleich, derzeit
kwargs = OrderedDict()
kwargs['eggs'] = ...
...
def spam(a, kwargs):
...
und mit diesem Vorschlag
def spam(a, **kwargs):
...
Alyssa (Nick) Coghlan fasste, als sie über einige der Anwendungsfälle sprach, es gut zusammen [2]
These *can* all be done today, but *not* by using keyword arguments.
In my view, the problem to be addressed is that keyword arguments
*look* like they should work for these cases, because they have a
definite order in the source code. The only reason they don't work
is because the interpreter throws that ordering information away.
It's a textbook case of a language feature becoming an attractive
nuisance in some circumstances: the simple and obvious solution for
the above use cases *doesn't actually work* for reasons that aren't
obviously clear if you don't have a firm grasp of Python's admittedly
complicated argument handling.
Diese Beobachtung wird durch das Auftreten dieses Vorschlags im Laufe der Jahre und die zahlreichen Male gestützt, in denen Leute vom Konstruktor für OrderedDict verwirrt waren. [3] [4] [5]
Anwendungsfälle
Wie Alyssa bemerkte, ist das aktuelle Verhalten von **kwargs in Fällen, in denen man erwarten würde, dass die Reihenfolge wichtig ist, unintuitiv. Abgesehen von spezifischeren unten dargelegten Fällen wird im Allgemeinen "alles andere, wo Sie die Iterationsreihenfolge steuern *und* Feldnamen und Werte in einem einzigen Aufruf festlegen möchten, potenziell profitieren." [6] Das ist im Falle von Fabriken (z. B. __init__()) für geordnete Typen wichtig.
Serialisierung
Offensichtlich würde OrderedDict (sowohl __init__() als auch update()) von geordneten kwargs profitieren. Der Vorteil erstreckt sich jedoch auch auf Serialisierungs-APIs [2]
In the context of serialisation, one key lesson we have learned is
that arbitrary ordering is a problem when you want to minimise
spurious diffs, and sorting isn't a simple solution.
Tools like doctest don't tolerate spurious diffs at all, but are
often amenable to a sorting based answer.
The cases where it would be highly desirable to be able use keyword
arguments to control the order of display of a collection of key
value pairs are ones like:
* printing out key:value pairs in CLI output
* mapping semantic names to column order in a CSV
* serialising attributes and elements in particular orders in XML
* serialising map keys in particular orders in human readable formats
like JSON and YAML (particularly when they're going to be placed
under source control)
Debugging
In den Worten von Raymond Hettinger [7]
It makes it easier to debug if the arguments show-up in the order
they were created. AFAICT, no purpose is served by scrambling them.
Andere Anwendungsfälle
- Mock-Objekte. [8]
- Steuerung der Objektdarstellung.
- Alternative namedtuple() mit optionalen Standardwerten.
- Festlegen der Argumentpriorität nach Reihenfolge.
Bedenken
Performance
Wie bereits erwähnt, kam die Idee der geordneten Schlüsselwortargumente mehrmals auf. Jedes Mal wurde mit der gleichen Antwort reagiert, nämlich dass die Beibehaltung der Reihenfolge von Schlüsselwortargumenten die Leistung von Funktionsaufrufen erheblich beeinträchtigen würde, sodass es sich nicht lohnt. Guido bemerkte jedoch Folgendes [9]
Making **kwds ordered is still open, but requires careful design and
implementation to avoid slowing down function calls that don't benefit.
Wie unten erläutert, gibt es Möglichkeiten, dies auf Kosten erhöhter Komplexität zu umgehen. Letztendlich ist der einfachste Ansatz derjenige, der am meisten Sinn ergibt: gesammelte Schlüsselwortargumente in einem OrderedDict verpacken. Ohne eine C-Implementierung von OrderedDict gibt es jedoch nicht viel zu diskutieren. Das hat sich in Python 3.5 geändert. [10]
Hinweis: In Python 3.6 ist dict reihenfolgebewahrend. Dies eliminiert praktisch Leistungsprobleme.
Andere Python-Implementierungen
Ein weiterer wichtiger Punkt, der berücksichtigt werden muss, ist, dass neue Funktionen mehrere Python-Implementierungen berücksichtigen müssen. Irgendwann wird von jeder von ihnen erwartet, dass sie geordnete kwargs implementiert haben. In dieser Hinsicht scheint es kein Problem mit der Idee zu geben. [11] Eine informelle Umfrage der wichtigsten Python-Implementierungen hat ergeben, dass diese Funktion keine signifikante Belastung darstellt.
Spezifikation
Ab Version 3.6 behält Python die Reihenfolge der Schlüsselwortargumente bei, wie sie an eine Funktion übergeben werden. Um dies zu erreichen, sind die gesammelten kwargs nun eine geordnete Zuordnung. Beachten Sie, dass dies nicht unbedingt OrderedDict bedeutet. dict in CPython 3.6 ist jetzt geordnet, ähnlich wie PyPy.
Dies gilt nur für Funktionen, deren Definition die **kwargs-Syntax zur Erfassung anderweitig nicht spezifizierter Schlüsselwortargumente verwendet. Nur die Reihenfolge dieser Schlüsselwortargumente wird beibehalten.
Beziehung zur **-Unpacking-Syntax
Die **-Unpacking-Syntax bei Funktionsaufrufen hat keine spezielle Verbindung zu diesem Vorschlag. Schlüsselwortargumente, die durch Entpacken bereitgestellt werden, werden genauso behandelt wie bisher: diejenigen, die definierten Parametern entsprechen, werden dort gesammelt, und der Rest wird in den geordneten kwargs gesammelt (genau wie jedes andere nicht übereinstimmende Schlüsselwortargument).
Beachten Sie, dass das Entpacken einer Zuordnung mit undefinierter Reihenfolge, wie z. B. dict, seine Iterationsreihenfolge wie gewohnt beibehält. Es ist nur so, dass die Reihenfolge undefiniert bleibt. Die geordnete Zuordnung, in die die entpackten Schlüssel-Wert-Paare dann verpackt werden, kann keine alternative Reihenfolge bieten. Dies sollte nicht überraschen.
Es gab kurze Diskussionen darüber, diese Zuordnungen einfach an die kwargs der Funktionen weiterzugeben, ohne sie zu entpacken und neu zu verpacken, aber das liegt außerhalb des Umfangs dieses Vorschlags und ist wahrscheinlich ohnehin eine schlechte Idee. (Es gibt einen Grund, warum diese Diskussionen kurz waren.)
Beziehung zu inspect.Signature
Signatur-Objekte müssen keine Änderungen erfahren. Der kwargs-Parameter von inspect.BoundArguments (zurückgegeben von Signature.bind() und Signature.bind_partial()) ändert sich von einem dict zu einem OrderedDict.
C-API
Keine Änderungen.
Syntax
Durch diesen Vorschlag werden keine Syntax hinzugefügt oder geändert.
Abwärtskompatibilität
Das Folgende ändert sich
- Die Iterationsreihenfolge von kwargs ist nun konsistent (außer natürlich im oben beschriebenen Fall)
Referenzimplementierung
Für CPython gibt es nichts zu tun.
Alternative Ansätze
Opt-out-Decorator
Dies ist identisch mit dem aktuellen Vorschlag, mit der Ausnahme, dass Python zusätzlich einen Decorator in functools bereitstellen würde, der dazu führt, dass gesammelte Schlüsselwortargumente in ein normales dict anstelle eines OrderedDict verpackt werden.
Prognose
Dies wäre nur notwendig, wenn festgestellt wird, dass die Leistung in einigen unüblichen Fällen signifikant unterschiedlich ist oder dass es andere Kompatibilitätsprobleme gibt, die nicht anders gelöst werden können.
Opt-in-Decorator
Der Status quo würde unverändert bleiben. Stattdessen würde Python einen Decorator in functools bereitstellen, der die dekorierte Funktion als eine kennzeichnet, die geordnete Schlüsselwortargumente erhalten soll. Der Leistungsoverhead für die Überprüfung der Funktion beim Aufruf wäre marginal.
Prognose
Der einzige wirkliche Nachteil sind Funktions-Wrapper-Fabriken (z. B. functools.partial und viele Decorators), die darauf abzielen, Schlüsselwortargumente perfekt beizubehalten, indem sie kwargs in der Wrapper-Definition und kwargs-Unpacking im Aufruf der gewrappten Funktion verwenden. Jeder Wrapper müsste separat aktualisiert werden, obwohl functools.wraps() dies automatisch tun würde, wäre es hilfreich.
__kworder__
Die Reihenfolge der Schlüsselwortargumente würde zur Laufzeit separat in einer Liste gespeichert. Die Liste würde in den Funktionslokalen an __kworder__ gebunden.
Prognose
Dies verkompliziert ebenfalls den Wrapper-Fall.
Kompakter Dict mit schnellerer Iteration
Raymond Hettinger hat die Idee einer dict-Implementierung eingeführt, die die Einfügungsreihenfolge bei dicts (bis zur ersten Löschung) beibehält. Dies wäre eine perfekte Übereinstimmung für kwargs. [5]
Prognose
Die Idee ist sowohl hinsichtlich Machbarkeit als auch Zeitplan noch unsicher.
Beachten Sie, dass Python 3.6 nun über diese dict-Implementierung verfügt.
***kwargs
Dies würde eine neue Form für die Signatur einer Funktion als wechselseitig ausschließendes Parallel zu **kwargs hinzufügen. Die neue Syntax, ***kwargs (beachten Sie die drei Sternchen), würde anzeigen, dass kwargs die Reihenfolge der Schlüsselwortargumente beibehalten soll.
Prognose
Neue Syntax wird nur unter den **dringendsten** Umständen zu Python hinzugefügt. Mit anderen verfügbaren Lösungen ist neue Syntax nicht gerechtfertigt. Darüber hinaus würde die neue Syntax, wie alle Opt-in-Lösungen, den Weiterleitungsfall komplizieren.
Annotationen
Dies ist eine Variante des Decorator-Ansatzes. Anstatt einen Decorator zu verwenden, um die Funktion zu kennzeichnen, würden Sie eine Funktionsannotation an **kwargs verwenden.
Prognose
Zusätzlich zur Komplizierung der Weiterleitung wurden Annotationen in der Kernentwicklung von Python aktiv entmutigt. Die Verwendung von Annotationen zur Aktivierung der Reihenfolgebewahrung birgt das Risiko, andere Anwendungsfälle von Annotationen zu stören.
dict.__order__
dict-Objekte hätten ein neues Attribut, __order__, das standardmäßig None wäre und das der Interpreter im kwargs-Fall auf die gleiche Weise verwenden würde, wie oben für __kworder__ beschrieben.
Prognose
Es hätte keinerlei Auswirkungen auf die kwargs-Leistung, aber die Änderung wäre ziemlich aufdringlich (Python verwendet dict viel). Außerdem müsste der Interpreter im Fall von Wrappern vorsichtig sein, um __order__ beizubehalten.
KWArgsDict.__order__
Dies ist dasselbe wie die Idee dict.__order__, aber kwargs wären eine Instanz einer neuen minimalen dict-Unterklasse, die das Attribut __order__ bereitstellt. dict würde stattdessen unverändert bleiben.
Prognose
Ein einfacher Wechsel zu OrderedDict ist eine weniger komplizierte und intuitivere Änderung.
Danksagungen
Vielen Dank an Andrew Barnert für hilfreiches Feedback und an die Teilnehmer aller vergangenen E-Mail-Threads.
Fußnoten
Referenzen
https://mail.python.org/pipermail/python-ideas/2010-Oktober/008445.html
https://mail.python.org/pipermail/python-ideas/2011-Januar/009037.html
https://mail.python.org/pipermail/python-ideas/2013-Februar/019690.html
https://mail.python.org/pipermail/python-ideas/2013-Mai/020727.html
https://mail.python.org/pipermail/python-ideas/2014-März/027225.html
http://bugs.python.org/issue16276
http://bugs.python.org/issue16553
https://mail.python.org/pipermail/python-dev/2013-Mai/126327.html
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0468.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT