PEP 276 – Einfacher Iterator für ints
- Autor:
- Jim Althoff <james_althoff at i2.com>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 12. Nov. 2001
- Python-Version:
- 2.3
- Post-History:
Zusammenfassung
Python 2.1 fügte neue Funktionalität zur Unterstützung von Iteratoren hinzu (PEP 234). Iteratoren haben sich in vielen Codierungssituationen als nützlich und praktisch erwiesen. Es ist anzumerken, dass die Implementierung der for-Schleifen-Kontrollstruktur von Python ab Version 2.1 das Iteratorprotokoll verwendet. Es ist auch anzumerken, dass Python Iteratoren für die folgenden eingebauten Typen bereitstellt: Listen, Tupel, Dictionaries, Strings und Dateien. Diese PEP schlägt die Hinzufügung eines Iterators für den eingebauten Typ int (types.IntType) vor. Ein solcher Iterator würde die Codierung bestimmter for-Schleifen in Python vereinfachen.
BDFL-Verkündigung
Diese PEP wurde am 17. Juni 2005 mit einer Notiz an python-dev abgelehnt.
Ein Großteil des ursprünglichen Bedarfs wurde durch die Funktion enumerate() gedeckt, die für Python 2.3 übernommen wurde.
Außerdem erlaubte der Vorschlag und ermutigte zu Missbräuchen wie
>>> for i in 3: print i
0
1
2
Ebenso war es nicht hilfreich, dass der Vorschlag die Syntaxfehler in Anweisungen wie
x, = 1
Spezifikation
Definieren Sie einen Iterator für types.intType (d. h. den eingebauten Typ "int"), der von der eingebauten Funktion "iter" zurückgegeben wird, wenn sie mit einer Instanz von types.intType als Argument aufgerufen wird.
Der zurückgegebene Iterator hat folgendes Verhalten
- Angenommen, Objekt i ist eine Instanz von
types.intType(dem eingebauten Typ int) und i > 0 iter(i)gibt ein Iteratorobjekt zurück- das besagte Iteratorobjekt iteriert über die Sequenz von ints 0,1,2,…,i-1
Beispiel
iter(5)gibt ein Iteratorobjekt zurück, das über die Sequenz von ints 0,1,2,3,4 iteriert - wenn i <= 0, gibt
iter(i)einen "leeren" Iterator zurück, d.h. einen, der StopIteration beim ersten Aufruf seiner "next"-Methode auslöst
Mit anderen Worten, die Bedingungen und Semantiken des besagten Iterators sind konsistent mit den Bedingungen und Semantiken der Funktionen range() und xrange().
Beachten Sie, dass die Sequenz 0,1,2,…,i-1, die mit dem int i assoziiert ist, im Kontext der Python-Programmierung als "natürlich" angesehen wird, da sie mit dem eingebauten Indexierungsprotokoll von Sequenzen in Python konsistent ist. Python-Listen und Tupel werden zum Beispiel bei 0 indiziert und enden bei len(object)-1 (bei Verwendung positiver Indizes). Mit anderen Worten, solche Objekte werden mit der Sequenz 0,1,2,…,len(object)-1 indiziert.
Begründung
Ein gängiges Programmieridiom ist es, eine Sammlung von Objekten zu nehmen und eine Operation auf jedes Element in der Sammlung in einer etablierten Reihenfolge anzuwenden. Python stellt die "for in"-Schleifen-Kontrollstruktur zur Handhabung dieses gängigen Idioms bereit. Es ergeben sich jedoch Fälle, in denen es notwendig (oder bequemer) ist, auf jedes Element in einer "indizierten" Sammlung zuzugreifen, indem man durch jeden Index iteriert und jedes Element in der Sammlung über den entsprechenden Index zugreift.
Man könnte zum Beispiel eine zweidimensionale "Tabellen"-Objekt haben, bei der die Anwendung einer Operation auf die erste Spalte jeder Zeile in der Tabelle erforderlich ist. Je nach Implementierung der Tabelle ist es möglicherweise nicht möglich, zuerst jede Zeile und dann jede Spalte als einzelne Objekte abzurufen. Es ist möglicherweise eher möglich, auf eine Zelle in der Tabelle über einen Zeilenindex und einen Spaltenindex zuzugreifen. In einem solchen Fall ist es notwendig, ein Idiom zu verwenden, bei dem man durch eine Sequenz von Indizes (Indexen) iteriert, um auf die gewünschten Elemente in der Tabelle zuzugreifen. (Beachten Sie, dass die gebräuchliche DefaultTableModel-Klasse in Java-Swing-Jython dieses sehr Protokoll aufweist).
Ein weiteres gängiges Beispiel ist, wenn man zwei oder mehr Sammlungen parallel verarbeiten muss. Ein weiteres Beispiel ist, wenn man z.B. jeden zweiten Eintrag einer Sammlung abrufen muss.
Es gibt viele andere Beispiele, bei denen der Zugriff auf Elemente in einer Sammlung durch eine Berechnung eines Index erleichtert wird, was den Zugriff auf die Indizes und nicht den direkten Zugriff auf die Elemente selbst erfordert.
Nennen wir dieses Idiom das "indizierte for-Schleifen"-Idiom. Einige Programmiersprachen bieten eine integrierte Syntax zur Handhabung dieses Idioms. In Python ist die übliche Konvention zur Implementierung des indizierten for-Schleifen-Idioms die Verwendung der integrierten Funktion range() oder xrange() zur Erzeugung einer Indexsequenz, wie zum Beispiel
for rowcount in range(table.getRowCount()):
print table.getValueAt(rowcount, 0)
oder
for rowcount in xrange(table.getRowCount()):
print table.getValueAt(rowcount, 0)
Von Zeit zu Zeit gibt es Diskussionen in der Python-Community über das indizierte for-Schleifen-Idiom. Es wird manchmal argumentiert, dass die Notwendigkeit, die Funktion range() oder xrange() für dieses Design-Idiom zu verwenden, ist
- Nicht offensichtlich (für neue Python-Programmierer),
- Fehleranfällig (leicht zu vergessen, selbst für erfahrene Python-Programmierer)
- Verwirrend und ablenkend für diejenigen, die gezwungen sind, die Unterschiede und die empfohlene Verwendung von
xrange()im Vergleich zurange()zu verstehen - Unhandlich, insbesondere in Kombination mit der Funktion
len(), d.h.xrange(len(sequence)) - Nicht so bequem wie entsprechende Mechanismen in anderen Sprachen,
- Nervig, ein "Warzen", etc.
Und von Zeit zu Zeit werden Vorschläge für Möglichkeiten unterbreitet, wie Python einen besseren Mechanismus für dieses Idiom bereitstellen könnte. Jüngste Beispiele sind PEP 204, "Range Literals", und PEP 212, "Loop Counter Iteration".
Meistens beinhalten solche Vorschläge Änderungen an der Python-Syntax und andere "schwergewichtige" Änderungen.
Ein Teil der Schwierigkeit hierbei ist, dass die Befürwortung neuer Syntax eine umfassende Lösung für "allgemeines Indizieren" impliziert, die Aspekte wie
- Anfangswert des Index
- Endwert des Index
- Schrittwert
- offene Intervalle versus geschlossene Intervalle versus halboffene Intervalle
Es hat sich als schwieriger als erwartet erwiesen, eine neue Syntax zu finden, die umfassend, einfach, allgemein, Pythonisch, ansprechend für viele, leicht zu implementieren, nicht im Konflikt mit bestehenden Strukturen, nicht übermäßig überladend für bestehende Strukturen usw. ist.
Der in dieser PEP dargelegte Vorschlag versucht, das Problem durch einen einfachen "leichtgewichtigen" Lösungsansatz anzugehen, der den häufigsten Fall mit einem bewährten Mechanismus löst, der bereits (ab Python 2.1) verfügbar ist: nämlich Iteratoren.
Da for-Schleifen ab Python 2.1 bereits das "Iterator"-Protokoll verwenden, würde die Hinzufügung eines Iterators für types.IntType, wie in dieser PEP vorgeschlagen, standardmäßig die folgende Abkürzung für das indizierte for-Schleifen-Idiom ermöglichen:
for rowcount in table.getRowCount():
print table.getValueAt(rowcount, 0)
Die folgenden Vorteile dieses Ansatzes im Vergleich zum aktuellen Mechanismus der Verwendung der Funktionen range() oder xrange() werden angeführt:
- Einfacher,
- Weniger überladen,
- Konzentriert sich auf das Problem, ohne auf sekundäre, implementierungsorientierte Funktionen (
range()undxrange()) zurückgreifen zu müssen
Und im Vergleich zu anderen Änderungsvorschlägen
- Erfordert keine neue Syntax
- Erfordert keine neuen Schlüsselwörter
- Nutzt den neuen und gut etablierten Iterator-Mechanismus
Und im Allgemeinen
- Ist konsistent mit bereits (ab Python 2.1) enthaltenen "Komfort"-Änderungen, die auf Iteratoren basieren, für andere eingebaute Typen wie Listen, Tupel, Dictionaries, Strings und Dateien.
Abwärtskompatibilität
Der vorgeschlagene Mechanismus ist im Allgemeinen abwärtskompatibel, da er weder eine neue Syntax noch neue Schlüsselwörter vorsieht. Alle bestehenden, gültigen Python-Programme sollten weiterhin unverändert funktionieren.
Dieser Vorschlag ist jedoch nicht perfekt abwärtskompatibel, da bestimmte Anweisungen, die derzeit ungültig sind, unter dem aktuellen Vorschlag gültig werden würden.
Tim Peters hat zwei solche Beispiele hervorgehoben
- Der übliche Fall, bei dem man vergisst,
range()oderxrange()einzufügen, zum Beispielfor rowcount in table.getRowCount(): print table.getValueAt(rowcount, 0)
löst in Python 2.2 eine TypeError-Ausnahme aus.
Unter dem aktuellen Vorschlag wäre die obige Anweisung gültig und würde (vermutlich) wie beabsichtigt funktionieren. Vermutlich ist dies eine gute Sache.
Wie von Tim bemerkt, ist dies der übliche Fall des Fehlers "vergessener Bereich" (den man derzeit durch Hinzufügen eines Aufrufs von
range()oderxrange()korrigiert). - Der (hoffentlich) sehr seltene Fall, bei dem man einen Tippfehler bei der Verwendung von Tupel-Unpacking macht. Zum Beispiel
x, = 1
löst in Python 2.2 eine
TypeError-Ausnahme aus.Unter dem aktuellen Vorschlag wäre die obige Anweisung gültig und würde x auf 0 setzen. Der PEP-Autor hat keine Daten darüber, wie häufig dieser Tippfehler auftritt oder wie schwierig es wäre, einen solchen Fehler unter dem aktuellen Vorschlag zu erkennen. Er stellt sich vor, dass er nicht häufig vorkommt und dass er, falls er auftritt, relativ einfach zu korrigieren wäre.
Probleme
Umfangreiche Diskussionen über PEP 276 auf der Python-Interessen-Mailingliste zeigen eine Bandbreite von Meinungen: einige dafür, einige neutral, einige dagegen. Die Befürworter neigen dazu, den oben genannten Behauptungen über die Nützlichkeit, Bequemlichkeit, Lernfähigkeit und Einfachheit eines einfachen Iterators für ganze Zahlen zuzustimmen.
Probleme mit PEP 276 umfassen
- Die Verwendung von range/xrange ist in Ordnung.
Antwort: Einige Poster vertreten diese Ansicht. Andere sind anderer Meinung.
- Einige sind der Meinung, dass das Iterieren über die Sequenz "0, 1, 2, ..., n-1" für eine ganze Zahl n nicht intuitiv ist. "for i in 5:" wird (von einigen) als "nicht offensichtlich" angesehen. Einige mögen diese Verwendung nicht, weil sie "nicht das richtige Gefühl" hat. Einige mögen sie nicht, weil sie glauben, dass diese Art der Verwendung dazu zwingt, ganze Zahlen als Sequenzen zu betrachten, was ihnen falsch erscheint. Einige mögen sie nicht, weil sie es vorziehen, for-Schleifen als Umgang mit expliziten Sequenzen und nicht mit beliebigen Iteratoren zu betrachten.
Antwort: Einige mögen das vorgeschlagene Idiom und sehen es als einfach, elegant, leicht zu lernen und einfach zu verwenden an. Einige sind bei diesem Thema neutral. Andere, wie erwähnt, mögen es nicht.
- Ist es offensichtlich, dass
iter(5)auf die Sequenz 0,1,2,3,4 abgebildet wird?Antwort: Angesichts der Tatsache, dass Python, wie oben erwähnt, eine starke Konvention für die Indizierung von Sequenzen hat, die bei 0 beginnt und beim Index stoppt, dessen Wert eins kleiner als die Länge der Sequenz ist (einschließlich), wird argumentiert, dass die vorgeschlagene Sequenz für den Python-Programmierer einigermaßen intuitiv ist und gleichzeitig nützlich und praktisch. Wichtiger ist, dass argumentiert wird, dass diese Konvention, sobald sie gelernt wurde, sehr leicht zu merken ist. Beachten Sie, dass die Dokumentation der range-Funktion auf die natürliche und nützliche Verbindung zwischen
range(n)und den Indizes für eine Liste der Länge n verweist. - Mögliche Mehrdeutigkeit
for i in 10: print i
könnte verwechselt werden mit
for i in (10,): print i
Antwort: Dies ist genau die gleiche Situation wie bei Strings im aktuellen Python (ersetzen Sie 10 durch 'spam' im obigen Beispiel, zum Beispiel).
- Zu allgemein: In den neuesten Python-Versionen gibt es Kontexte – wie bei for-Schleifen –, in denen Iteratoren implizit aufgerufen werden. Einige befürchten, dass die Aktivierung eines Iterators für eine ganze Zahl in einem der Kontexte (außer für for-Schleifen) zu unerwartetem Verhalten und Fehlern führen könnte. Das oben genannte Beispiel "x, = 1" ist ein solcher Fall.
Antwort: Aus Sicht des Autors schienen die oben identifizierten Beispiele in den Diskussionen zu PEP 276 keine solchen zu sein, die versehentlich auf eine Weise missbraucht würden, die zu subtilen und schwer zu entdeckenden Fehlern führen würde.
Darüber hinaus scheint es eine Möglichkeit zu geben, dieses Problem zu lösen, indem man eine Variante dessen verwendet, was im Spezifikationsabschnitt dieses Vorschlags dargelegt ist. Anstatt eine
__iter__-Methode zur Klasse int hinzuzufügen, sollte der Code für die for-Schleifen-Verarbeitung geändert werden, um (im Wesentlichen) zu konvertieren vonfor i in n: # when isinstance(n,int) is 1
zu
for i in xrange(n):
Dieser Ansatz liefert die gleichen Ergebnisse in einer for-Schleife wie eine
__iter__-Methode, würde aber die Iteration über ganze Zahlen in anderen Kontexten verhindern. Listen und Tupel zum Beispiel haben keine__iter__-Methode und werden mit speziellem Code behandelt. Ganze Zahlen wären ein weiterer Sonderfall. - "i in n" erscheint sehr unnatürlich.
Antwort: Einige sind der Meinung, dass "i in len(mylist)" leicht verständlich und nützlich wäre. Einige mögen es nicht, insbesondere wenn ein Literal verwendet wird, wie in "i in 5". Wenn die im Antwort auf das vorherige Problem erwähnte Variante implementiert wird, ist dieses Problem hinfällig. Wenn nicht, könnte dieses Problem auch dadurch behoben werden, dass eine
__contains__-Methode in der Klasse int definiert wird, die immer einen TypeError auslöst. Dies würde das Verhalten von "i in n" identisch mit dem aktuellen Python machen. - Könnte Anfänger davon abhalten, das indizierte for-Schleifen-Idiom zu verwenden, wenn das Standardidiom "for item in collection:" eindeutig besser ist.
Antwort: Das Standardidiom ist so schön, wenn es passt, dass es weder zusätzlichen "Karotten"- noch "Stöckchen"-Anreiz braucht. Andererseits bemerkt man Fälle von Über- oder Missbrauch des Standardidioms (wahrscheinlich aufgrund der Umständlichkeit des indizierten for-Schleifen-Idioms), wie zum Beispiel
for item in sequence: print sequence.index(item)
- Warum nicht noch größere Änderungen vorschlagen?
Die Mehrheit der Meinungsverschiedenheiten mit PEP 276 kam von denen, die viel größere Änderungen an Python befürworten, um das allgemeinere Problem der Spezifikation einer Sequenz von ganzen Zahlen zu lösen, wobei eine solche Spezifikation umfassend genug ist, um den Anfangswert, den Endwert und den Schrittwert der Sequenz zu handhaben, und auch Variationen von offenen, geschlossenen und halboffenen (halboffenen) Ganzzahlintervallen berücksichtigt. Viele Vorschläge dazu wurden diskutiert.
Diese beinhalten
- Haskell-ähnliche Notation zur Angabe einer Sequenz von ganzen Zahlen in einer literalen Liste,
- verschiedene Verwendungen von Slicing-Notation zur Angabe von Sequenzen,
- Änderungen an der Syntax von for-in-Schleifen, um die Verwendung von relationalen Operatoren im Schleifenkopf zu ermöglichen,
- Erstellung einer Ganzzahlintervallklasse zusammen mit Methoden, die relationale Operatoren oder Divisionsoperatoren überladen, um "Slicing" auf Ganzzahlintervallobjekten bereitzustellen,
- und mehr.
Es sei darauf hingewiesen, dass es viel Debatte, aber keinen überwältigenden Konsens für einen dieser groß angelegten Vorschläge gab.
Offensichtlich schlägt PEP 276 keine solch umfangreiche Änderung vor und konzentriert sich stattdessen auf einen spezifischen Problembereich. Gegen Ende der Diskussionsphase äußerten mehrere Poster ihre Zustimmung zum engen Fokus und zur Einfachheit von PEP 276 im Vergleich zu den vorangestellten ambitionierteren Vorschlägen. Es schien einen Konsens über die Notwendigkeit einer PEP für jeden solchen größeren, alternativen Vorschlag zu geben. In Anbetracht dieser Erkenntnis werden die Details der verschiedenen alternativen Vorschläge hier nicht weiter besprochen.
Implementierung
Eine Implementierung ist derzeit nicht verfügbar, wird aber als unkompliziert erwartet. Der Autor hat eine Unterklasse von int mit einer __iter__-Methode (in Python geschrieben) als Mittel zur Erprobung der Ideen in diesem Vorschlag implementiert.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0276.rst
Zuletzt geändert: 2025-02-01 08:55:40 GMT