PEP 448 – Zusätzliche Generalisierungen für Unpacking
- Autor:
- Joshua Landau <joshua at landau.ws>
- Discussions-To:
- Python-Ideas Liste
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 29-Jun-2013
- Python-Version:
- 3.5
- Post-History:
Inhaltsverzeichnis
Zusammenfassung
Diese PEP schlägt erweiterte Verwendungen des Iterablen-Unpacking-Operators * und des Dictionary-Unpacking-Operators ** vor, um das Unpacking an mehr Positionen, beliebig oft und in Funktionsaufrufen und Displays zu ermöglichen.
Funktionsaufrufe sollen eine beliebige Anzahl von Unpackings unterstützen und nicht nur eines.
>>> print(*[1], *[2], 3)
1 2 3
>>> dict(**{'x': 1}, y=2, **{'z': 3})
{'x': 1, 'y': 2, 'z': 3}
Unpacking soll in Tupel-, Listen-, Mengen- und Dictionary-Displays erlaubt sein.
>>> *range(4), 4
(0, 1, 2, 3, 4)
>>> [*range(4), 4]
[0, 1, 2, 3, 4]
>>> {*range(4), 4}
{0, 1, 2, 3, 4}
>>> {'x': 1, **{'y': 2}}
{'x': 1, 'y': 2}
In Dictionaries überschreiben spätere Werte immer frühere.
>>> {'x': 1, **{'x': 2}}
{'x': 2}
>>> {**{'x': 2}, 'x': 1}
{'x': 1}
Diese PEP beinhaltet keine Unpacking-Operatoren innerhalb von Listen-, Mengen- und Dictionary-Comprehensions, obwohl dies für zukünftige Vorschläge nicht ausgeschlossen wurde.
Begründung
Die aktuelle Verwendung des Iterablen-Unpacking-Operators * weist unnötige Einschränkungen auf, die die Lesbarkeit beeinträchtigen können.
Mehrfaches Unpacking hat eine offensichtliche Begründung. Wenn Sie mehrere Iterables in eine Funktionsdefinition entpacken möchten oder nach einem Unpack weitere Positionsargumente folgen lassen möchten, wäre der natürlichste Weg, Folgendes zu schreiben:
function(**kw_arguments, **more_arguments)
function(*arguments, argument)
Einfache Beispiele, wo dies nützlich ist, sind print und str.format. Stattdessen könnten Sie gezwungen sein, Folgendes zu schreiben:
kwargs = dict(kw_arguments)
kwargs.update(more_arguments)
function(**kwargs)
args = list(arguments)
args.append(arg)
function(*args)
oder, wenn Sie es wissen:
from collections import ChainMap
function(**ChainMap(more_arguments, arguments))
from itertools import chain
function(*chain(args, [arg]))
was unnötiges Zeilenrauschen hinzufügt und bei den ersten Methoden eine doppelte Arbeit verursacht.
Es gibt zwei Hauptgründe für das Unpacking innerhalb von Containern. Erstens gibt es eine Symmetrie der Zuweisung, bei der fst, *other, lst = elems und elems = fst, *other, lst annähernde Inverse sind, abgesehen von den Spezifika der Typen. Dies vereinfacht im Wesentlichen die Sprache, indem Sonderfälle entfernt werden.
Zweitens vereinfacht es die Arten von "Addition" wie das Kombinieren von Dictionaries erheblich und tut dies auf eindeutige und gut definierte Weise
combination = {**first_dictionary, "x": 1, "y": 2}
anstatt
combination = first_dictionary.copy()
combination.update({"x": 1, "y": 2})
was besonders in Kontexten wichtig ist, in denen Ausdrücke bevorzugt werden. Dies ist auch nützlich als lesbarere Art, Iterables zu einer Liste zu summieren, wie z.B. my_list + list(my_tuple) + list(my_range), was nun äquivalent zu nur [*my_list, *my_tuple, *my_range] ist.
Spezifikation
Funktionsaufrufe können eine unbegrenzte Anzahl von * und ** Unpackings akzeptieren. Es wird keine Beschränkung der Reihenfolge von Positionsargumenten in Bezug auf * Unpackings geben, noch eine Beschränkung der Reihenfolge von Schlüsselwortargumenten in Bezug auf ** Unpackings.
Funktionsaufrufe behalten die Beschränkung bei, dass Schlüsselwortargumente auf Positionsargumente folgen müssen und ** Unpackings zusätzlich auf * Unpackings folgen müssen.
Derzeit wird, wenn ein Argument mehrmals übergeben wird – wie ein Positionsargument, das sowohl positionell als auch per Schlüsselwort übergeben wird – ein TypeError ausgelöst. Dies gilt weiterhin für doppelte Argumente, die über mehrere ** Unpackings bereitgestellt werden, z.B. f(**{'x': 2}, **{'x': 3}), mit der Ausnahme, dass der Fehler zur Laufzeit erkannt wird.
Eine Funktion sieht so aus:
function(
argument or *args, argument or *args, ...,
kwargument or *args, kwargument or *args, ...,
kwargument or **kwargs, kwargument or **kwargs, ...
)
Tupel, Listen, Mengen und Dictionaries werden das Unpacking zulassen. Dies wirkt so, als ob die Elemente aus entpackten Elementen in der Reihenfolge an der Stelle des Unpackings eingefügt wurden, ähnlich wie beim Unpacking in einem Funktionsaufruf. Dictionaries erfordern ** Unpacking; alle anderen erfordern * Unpacking.
Die Schlüssel in einem Dictionary behalten eine von rechts nach links wirkende Prioritätsreihenfolge bei, sodass {**{'a': 1}, 'a': 2, **{'a': 3}} zu {'a': 3} ausgewertet wird. Es gibt keine Beschränkung für die Anzahl oder Position von Unpackings.
Nachteile
Die zulässigen Reihenfolgen für Argumente in einem Funktionsaufruf sind komplizierter als zuvor. Die einfachste Erklärung für die Regeln mag sein: „Positionsargumente gehen Schlüsselwortargumenten und ** Unpacking voraus; * Unpacking geht ** Unpacking voraus“.
Während *elements, = iterable dazu führt, dass elements eine Liste ist, führt elements = *iterable, dazu, dass elements ein Tupel ist. Der Grund dafür kann Leute verwirren, die mit dem Konstrukt nicht vertraut sind.
Bedenken wurden hinsichtlich des unerwarteten Unterschieds zwischen der Zulässigkeit doppelter Schlüssel in Dictionaries und dem Fehler, der bei doppelten Schlüsseln in der Syntax von Funktionsaufrufen auftritt, geäußert. Obwohl dies bei der aktuellen Syntax bereits der Fall ist, könnte dieser Vorschlag das Problem verschärfen. Es bleibt abzuwarten, wie groß dieses Problem in der Praxis sein wird.
Variationen
Die PEP berücksichtigte ursprünglich, ob die Reihenfolge der Argumenttypen in einem Funktionsaufruf (positionell, Schlüsselwort, * oder **) weniger streng werden könnte. Dies stieß auf wenig Unterstützung, daher wurde die Idee verworfen.
Frühere Iterationen dieser PEP erlaubten Unpacking-Operatoren innerhalb von Listen-, Mengen- und Dictionary-Comprehensions als Flattening-Operator über Iterables von Containern.
>>> ranges = [range(i) for i in range(5)]
>>> [*item for item in ranges]
[0, 0, 1, 0, 1, 2, 0, 1, 2, 3]
>>> {*item for item in ranges}
{0, 1, 2, 3}
Dies stieß auf eine Mischung aus starken Bedenken hinsichtlich der Lesbarkeit und milder Unterstützung. Um die weniger umstrittenen Aspekte der PEP nicht zu benachteiligen, wurde dies nicht mit dem Rest des Vorschlags akzeptiert.
Klammerlose Comprehensions in Funktionsaufrufen, wie f(x for x in it), sind bereits gültig. Diese könnten erweitert werden zu
f(*x for x in it) == f((*x for x in it))
f(**x for x in it) == f({**x for x in it})
Es war jedoch nicht klar, ob dies das beste Verhalten war oder ob es in die Argumente des Aufrufs an f entpackt werden sollte. Da dies wahrscheinlich verwirrend ist und nur von sehr geringem Nutzen, ist es nicht in dieser PEP enthalten. Stattdessen wird ein SyntaxError ausgelöst und es sollten Comprehensions mit expliziten Klammern verwendet werden.
Genehmigung
Diese PEP wurde von Guido am 25. Februar 2015 angenommen [1].
Implementierung
Eine Implementierung für Python 3.5 findet sich unter Issue 2292 im Bug-Tracker [2]. Diese enthält derzeit Unterstützung für Unpacking innerhalb von Comprehensions, was entfernt werden sollte.
Referenzen
[3] Diskussion auf der Python-Ideas-Liste, „list / array comprehensions extension“, Alexander Heger (https://mail.python.org/pipermail/python-ideas/2011-December/013097.html)
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0448.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT