PEP 204 – Bereichs-Literale
- Autor:
- Thomas Wouters <thomas at python.org>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 14-Jul-2000
- Python-Version:
- 2.0
- Post-History:
Einleitung
Dieser PEP beschreibt den "Bereichs-Literal"-Vorschlag für Python 2.0. Dieser PEP verfolgt den Status und die Zuständigkeit dieser Funktion, die für die Einführung in Python 2.0 vorgesehen ist. Er enthält eine Beschreibung der Funktion und skizziert die notwendigen Änderungen zur Unterstützung der Funktion. Dieser PEP fasst Diskussionen in Mailinglisten zusammen und bietet gegebenenfalls URLs für weitere Informationen. Die CVS-Revisionshistorie dieser Datei enthält die maßgebliche historische Aufzeichnung.
Listen-Bereiche
Bereiche sind Sequenzen von Zahlen mit fester Schrittweite, die oft in for-Schleifen verwendet werden. Die Python for-Schleife ist so konzipiert, dass sie direkt über eine Sequenz iteriert
>>> l = ['a', 'b', 'c', 'd']
>>> for item in l:
... print item
a
b
c
d
Diese Lösung ist jedoch nicht immer ratsam. Erstens treten Probleme auf, wenn die Sequenz im Körper der for-Schleife geändert wird, was dazu führt, dass die for-Schleife Elemente überspringt. Zweitens ist es nicht möglich, z. B. jedes zweite Element der Sequenz zu durchlaufen. Und drittens ist es manchmal notwendig, ein Element basierend auf seinem Index zu verarbeiten, der im obigen Konstrukt nicht ohne Weiteres verfügbar ist.
Für diese Fälle und andere, in denen ein Zahlenbereich gewünscht wird, bietet Python die eingebaute Funktion range, die eine Liste von Zahlen erstellt. Die Funktion range nimmt drei Argumente entgegen: *start*, *end* und *step*. *start* und *step* sind optional und haben standardmäßig die Werte 0 bzw. 1.
Die Funktion range erstellt eine Liste von Zahlen, beginnend bei *start*, mit einer Schrittweite von *step*, bis, aber nicht einschließlich, *end*, sodass range(10) eine Liste erzeugt, die genau 10 Elemente enthält, die Zahlen 0 bis 9.
Unter Verwendung der Funktion range würde das obige Beispiel wie folgt aussehen
>>> for i in range(len(l)):
... print l[i]
a
b
c
d
Oder um beim zweiten Element von l zu beginnen und ab da nur jedes zweite Element zu verarbeiten
>>> for i in range(1, len(l), 2):
... print l[i]
b
d
Es gibt mehrere Nachteile bei diesem Ansatz
- Klarheit des Zwecks: Das Hinzufügen eines weiteren Funktionsaufrufs, möglicherweise mit zusätzlicher Arithmetik zur Bestimmung der gewünschten Länge und Schrittweite der Liste, verbessert die Lesbarkeit des Codes nicht. Außerdem ist es möglich, die eingebaute Funktion
rangezu "überschatten", indem eine lokale oder globale Variable mit demselben Namen bereitgestellt wird, wodurch sie effektiv ersetzt wird. Dies mag erwünscht sein oder auch nicht. - Effizienz: Da die Funktion
rangeüberschrieben werden kann, kann der Python-Compiler keine Annahmen über die for-Schleife treffen und muss einen separaten Schleifenzähler beibehalten. - Konsistenz: Es gibt bereits eine Syntax, die zur Darstellung von Bereichen verwendet wird, wie unten gezeigt. Diese Syntax verwendet exakt dieselben Argumente, obwohl alle optional sind, auf die gleiche Weise. Es erscheint logisch, diese Syntax auf Bereiche auszudehnen, um "Bereichs-Literale" zu bilden.
Slice-Indizes
In Python kann eine Sequenz auf eine von zwei Arten indiziert werden: Abrufen eines einzelnen Elements oder Abrufen eines Bereichs von Elementen. Das Abrufen eines Bereichs von Elementen ergibt ein neues Objekt desselben Typs wie die ursprüngliche Sequenz, das null oder mehr Elemente aus der ursprünglichen Sequenz enthält. Dies geschieht unter Verwendung einer "Bereichsnotation"
>>> l[2:4]
['c', 'd']
Diese Bereichsnotation besteht aus null, einem oder zwei durch einen Doppelpunkt getrennten Indizes. Der erste Index ist der *start*-Index, der zweite der *end*-Index. Wenn einer davon weggelassen wird, werden standardmäßig der Anfang bzw. das Ende der Sequenz verwendet.
Es gibt auch eine erweiterte Bereichsnotation, die auch die *Schrittweite* einbezieht. Obwohl diese Notation derzeit von den meisten eingebauten Typen nicht unterstützt wird, würde sie wie folgt funktionieren
>>> l[1:4:2]
['b', 'd']
Das dritte "Argument" der Slice-Syntax ist exakt dasselbe wie das *step*-Argument für range(). Die zugrunde liegenden Mechanismen der Standard- und dieser erweiterten Slices sind so unterschiedlich und inkonsistent, dass viele Klassen und Erweiterungen außerhalb mathematischer Pakete keine Unterstützung für die erweiterte Variante implementieren. Obwohl dies behoben werden sollte, liegt es außerhalb des Rahmens dieses PEP.
Erweiterte Slices zeigen jedoch, dass es bereits eine einwandfreie und anwendbare Syntax gibt, um Bereiche so darzustellen, dass alle früheren Nachteile der Verwendung der Funktion range() gelöst werden
- Es ist eine klarere, prägnantere Syntax, die sich bereits als intuitiv und leicht zu erlernen erwiesen hat.
- Sie ist konsistent mit der anderen Verwendung von Bereichen in Python (z. B. Slices).
- Da es sich um eine eingebaute Syntax handelt und nicht um eine eingebaute Funktion, kann sie nicht überschrieben werden. Das bedeutet sowohl, dass ein Betrachter sicher sein kann, was der Code tut, als auch, dass ein Optimierer sich keine Sorgen machen muss, dass
range()"überschattet" wird.
Die vorgeschlagene Lösung
Die vorgeschlagene Implementierung von Bereichs-Literalen kombiniert die Syntax für Listen-Literale mit der Syntax für (erweiterte) Slices, um Bereichs-Literale zu bilden
>>> [1:10]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> [:5]
[0, 1, 2, 3, 4]
>>> [5:1:-1]
[5, 4, 3, 2]
Es gibt einen geringfügigen Unterschied zwischen Bereichs-Literalen und der Slice-Syntax: Obwohl es möglich ist, alle von *start*, *end* und *step* in Slices wegzulassen, ist es nicht sinnvoll, *end* in Bereichs-Literalen wegzulassen. In Slices würde *end* standardmäßig das Ende der Liste sein, aber das hat in Bereichs-Literalen keine Bedeutung.
Referenzimplementierung
Die vorgeschlagene Implementierung ist auf SourceForge zu finden [1]. Sie fügt einen neuen Bytecode hinzu, BUILD_RANGE, der drei Argumente vom Stapel nimmt und auf deren Basis eine Liste erstellt. Die Liste wird wieder auf den Stapel gelegt.
Die Verwendung eines neuen Bytecodes ist notwendig, um Bereiche basierend auf anderen Berechnungen erstellen zu können, deren Ergebnis zur Kompilierzeit nicht bekannt ist.
Der Code führt zwei neue Funktionen in listobject.c ein, die sich derzeit zwischen privaten Funktionen und vollwertigen API-Aufrufen befinden.
PyList_FromRange() erstellt eine Liste aus Start, Ende und Schritt und gibt NULL zurück, wenn ein Fehler auftritt. Ihr Prototyp lautet
PyObject * PyList_FromRange(long start, long end, long step)
PyList_GetLenOfRange() ist eine Hilfsfunktion zur Bestimmung der Länge eines Bereichs. Zuvor war sie eine statische Funktion in bltinmodule.c, ist aber jetzt sowohl in listobject.c als auch in bltinmodule.c (für xrange) erforderlich. Sie wird nur nicht-statisch gemacht, um Code-Duplizierung zu vermeiden. Ihr Prototyp lautet
long PyList_GetLenOfRange(long start, long end, long step)
Offene Themen
- Eine mögliche Lösung für die Diskrepanz, das *end*-Argument in Bereichs-Literalen zu verlangen, ist die Zulassung der Bereichssyntax zur Erstellung eines "Generators" anstelle einer Liste, wie es die eingebaute Funktion
xrangetut. Ein Generator wäre jedoch keine Liste, und es wäre beispielsweise unmöglich, Elemente im Generator zuzuweisen oder Elemente an ihn anzuhängen.Die Bereichssyntax könnte konzeptionell auf Tupel (d. h. unveränderliche Listen) erweitert werden, die dann sicher als Generatoren implementiert werden könnten. Dies könnte eine wünschenswerte Lösung sein, insbesondere für große Zahlenarrays: Generatoren erfordern sehr wenig Speicher und Initialisierung, und es gibt nur einen geringen Leistungsaufwand bei der Berechnung und Erstellung der entsprechenden Zahl bei Bedarf. (TBD: Gibt es überhaupt einen? Oberflächliche Tests deuten auf gleiche Leistung hin, selbst bei Bereichen der Länge 1)
Wäre es jedoch ratsam, selbst wenn die Idee übernommen würde, das zweite Argument als "Sonderfall" zu behandeln und es in einem Fall der Syntax optional und in anderen Fällen zwingend zu machen?
- Sollte es möglich sein, Bereichssyntax mit normalen Listen-Literalen zu mischen und so eine einzelne Liste zu erstellen? Z.B.
>>> [5, 6, 1:6, 7, 9]
zu erstellen
[5, 6, 1, 2, 3, 4, 5, 7, 9]
- Wie sollten Bereichs-Literale mit einer anderen vorgeschlagenen neuen Funktion, "List Comprehensions", interagieren? Insbesondere, sollten Listen in List Comprehensions erstellt werden können? Z.B.
>>> [x:y for x in (1, 2) y in (3, 4)]
Sollte dieses Beispiel eine einzelne Liste mit mehreren Bereichen zurückgeben
[1, 2, 1, 2, 3, 2, 2, 3]
Oder eine Liste von Listen, so
[[1, 2], [1, 2, 3], [2], [2, 3]]
Da jedoch die Syntax und Semantik von List Comprehensions noch Gegenstand heißer Debatten sind, werden diese Probleme am besten vom PEP "List Comprehensions" behandelt.
- Bereichs-Literale akzeptieren andere Objekte als Integer: Sie führen
PyInt_AsLong()auf den übergebenen Objekten aus, so dass sie akzeptiert werden, solange die Objekte in Integer umgewandelt werden können. Die resultierende Liste besteht jedoch immer aus Standard-Integern.Sollten Bereichs-Literale eine Liste des übergebenen Typs erstellen? Dies könnte in den Fällen anderer eingebauter Typen wie Longs und Strings wünschenswert sein
>>> [ 1L : 2L<<64 : 2<<32L ] >>> ["a":"z":"b"] >>> ["a":"z":2]
Dies könnte jedoch zu viel "Magie" sein, um offensichtlich zu sein. Es könnte auch Probleme mit benutzerdefinierten Klassen aufwerfen: Selbst wenn die Basisklasse gefunden und eine neue Instanz erstellt werden kann, erfordert die Instanz möglicherweise zusätzliche Argumente für
__init__, was die Erstellung fehlschlagen lässt. - Die Funktionen
PyList_FromRange()undPyList_GetLenOfRange()müssen klassifiziert werden: Sind sie Teil der API oder sollten sie private Funktionen sein?
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Referenzen
Quelle: https://github.com/python/peps/blob/main/peps/pep-0204.rst
Zuletzt geändert: 2024-04-14 20:08:31 GMT