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

Python Enhancement Proposals

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

Inhaltsverzeichnis

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


Quelle: https://github.com/python/peps/blob/main/peps/pep-0468.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT