PEP 479 – Änderung der Behandlung von StopIteration in Generatoren
- Autor:
- Chris Angelico <rosuav at gmail.com>, Guido van Rossum <guido at python.org>
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 15. Nov. 2014
- Python-Version:
- 3.5
- Post-History:
- 15. Nov. 2014, 19. Nov. 2014, 05. Dez. 2014
Inhaltsverzeichnis
- Zusammenfassung
- Akzeptanz
- Begründung
- Hintergrundinformationen
- Vorschlag
- Konsequenzen für bestehenden Code
- Erläuterung von Generatoren, Iteratoren und StopIteration
- Übergangsplan
- Alternative Vorschläge
- Auslösen von etwas anderem als RuntimeError
- Bereitstellung einer spezifischen Ausnahme zum Auslösen bei Rückgabe
- StopIterationen, die durch Rückgabe ausgelöst werden, offensichtlich machen
- Konvertierung der Ausnahme innerhalb von next()
- Untervorschlag: Dekorator zur expliziten Anforderung des aktuellen Verhaltens
- Kritik
- Referenzen
- Urheberrecht
Zusammenfassung
Dieser PEP schlägt eine Änderung für Generatoren vor: Wenn StopIteration innerhalb eines Generators ausgelöst wird, wird sie durch RuntimeError ersetzt. (Genauer gesagt geschieht dies, wenn die Ausnahme kurz davor steht, aus dem Stack Frame des Generators herauszublasen.) Da die Änderung abwärts inkompatibel ist, wird die Funktion zunächst mit einer __future__-Anweisung eingeführt.
Akzeptanz
Dieser PEP wurde vom BDFL am 22. November akzeptiert. Aufgrund der außergewöhnlich kurzen Zeitspanne vom ersten Entwurf bis zur Akzeptanz wurden die wichtigsten Einwände, die nach der Akzeptanz geäußert wurden, sorgfältig geprüft und im Abschnitt „Alternative Vorschläge“ unten berücksichtigt. Keine der Diskussionen änderte jedoch die Meinung des BDFL, und die Akzeptanz des PEP ist nun endgültig. (Vorschläge für klärende Bearbeitungen sind weiterhin willkommen – im Gegensatz zu IETF RFCs ist der Text eines PEPs nach seiner Akzeptanz nicht in Stein gemeißelt, obwohl das Kerndesign/der Kernplan/die Kernspezifikation nach der Akzeptanz nicht mehr geändert werden sollten.)
Begründung
Die Interaktion von Generatoren und StopIteration ist derzeit etwas überraschend und kann obskure Fehler verbergen. Eine unerwartete Ausnahme sollte nicht zu subtil verändertem Verhalten führen, sondern eine laute und leicht zu debuggende Traceback erzeugen. Derzeit wird StopIteration, die versehentlich innerhalb einer Generatorfunktion ausgelöst wird, von der Schleifenkonstruktion, die den Generator antreibt, als Ende der Iteration interpretiert.
Das Hauptziel des Vorschlags ist es, das Debugging in der Situation zu erleichtern, in der ein ungeschützter next()-Aufruf (vielleicht mehrere Stack Frames tief) StopIteration auslöst und die vom Generator gesteuerte Iteration lautlos beendet. (Während beim Auslösen einer anderen Ausnahme eine Traceback gedruckt wird, die die Ursache des Problems hervorhebt.)
Dies ist besonders tückisch in Kombination mit dem yield from Konstrukt von PEP 380, da es die Abstraktion bricht, dass ein Subgenerator aus einem Generator herausgezogen werden kann. Dieser PEP weist auf diese Einschränkung hin, merkt aber an, dass „Anwendungsfälle dafür selten bis nicht vorhanden sind“. Unglücklicherweise ist, während die beabsichtigte Verwendung selten ist, es leicht, versehentlich auf diese Fälle zu stoßen.
import contextlib
@contextlib.contextmanager
def transaction():
print('begin')
try:
yield from do_it()
except:
print('rollback')
raise
else:
print('commit')
def do_it():
print('Refactored initial setup')
yield # Body of with-statement is executed here
print('Refactored finalization of successful transaction')
def gene():
for i in range(2):
with transaction():
yield i
# return
raise StopIteration # This is wrong
print('Should not be reached')
for i in gene():
print('main: i =', i)
Hier hat das Herausziehen von do_it in einen Subgenerator einen subtilen Fehler eingeführt: Wenn der umhüllte Block StopIteration auslöst, wird diese Ausnahme unter dem aktuellen Verhalten vom Context Manager verschluckt; und schlimmer noch, die Finalisierung wird lautlos übersprungen! Ähnlich problematisches Verhalten tritt auf, wenn eine asyncio-Koroutine StopIteration auslöst, was zu einem lautlosen Abbruch führt, oder wenn next verwendet wird, um das erste Ergebnis aus einem Iterator zu entnehmen, der unerwartet leer ist, zum Beispiel.
# using the same context manager as above
import pathlib
with transaction():
print('commit file {}'.format(
# I can never remember what the README extension is
next(pathlib.Path('/some/dir').glob('README*'))))
In beiden Fällen bricht die Refactoring-Abstraktion von yield from bei Fehlern im Client-Code.
Darüber hinaus reduziert der Vorschlag den Unterschied zwischen List Comprehensions und Generator Expressions und verhindert Überraschungen wie die, die diese Diskussion ausgelöst hat [2]. Von nun an werden die folgenden Anweisungen das gleiche Ergebnis liefern, wenn beide überhaupt ein Ergebnis liefern.
a = list(F(x) for x in xs if P(x))
a = [F(x) for x in xs if P(x)]
Mit dem aktuellen Stand der Dinge ist es möglich, eine Funktion F(x) oder ein Prädikat P(x) zu schreiben, die die erste Form zu einem (abgeschnittenen) Ergebnis führen, während die zweite Form eine Ausnahme auslöst (nämlich StopIteration). Mit der vorgeschlagenen Änderung lösen beide Formen an dieser Stelle eine Ausnahme aus (wenn auch RuntimeError im ersten Fall und StopIteration im zweiten).
Schließlich klärt der Vorschlag auch die Verwirrung darüber, wie ein Generator beendet wird: Der richtige Weg ist return, nicht raise StopIteration.
Als zusätzlicher Bonus nähern die oben genannten Änderungen Generatorfunktionen den normalen Funktionen stark an. Wenn Sie ein Stück Code, das als Generator präsentiert wird, in etwas anderes umwandeln möchten, können Sie dies normalerweise recht einfach tun, indem Sie jedes yield durch einen Aufruf von print() oder list.append() ersetzen; wenn es jedoch nackte next()-Aufrufe im Code gibt, müssen Sie sich dieser bewusst sein. Wenn der Code ursprünglich geschrieben wurde, ohne sich auf StopIteration zu verlassen, um die Funktion zu beenden, wäre die Transformation umso einfacher.
Hintergrundinformationen
Wenn ein Generator-Frame als Ergebnis eines __next__() (oder send() oder throw()) Aufrufs (neu) gestartet wird, kann eines von drei Ergebnissen eintreten:
- Ein Yield-Punkt wird erreicht und der Yield-Wert wird zurückgegeben.
- Der Frame wird verlassen;
StopIterationwird ausgelöst. - Eine Ausnahme wird ausgelöst, die herausblubbert.
In den beiden letzteren Fällen wird der Frame aufgegeben (und das gi_frame-Attribut des Generatorobjekts auf None gesetzt).
Vorschlag
Wenn StopIteration kurz davor steht, aus einem Generator-Frame herauszublasen, wird sie durch RuntimeError ersetzt, was dazu führt, dass der next()-Aufruf (der den Generator aufgerufen hat) fehlschlägt und diese Ausnahme weitergibt. Von da an ist es wie jede andere Ausnahme. [3]
Dies beeinflusst das dritte Ergebnis, das oben aufgeführt ist, ohne andere Auswirkungen zu verändern. Darüber hinaus beeinflusst es dieses Ergebnis nur, wenn die ausgelöste Ausnahme StopIteration (oder eine Unterklasse davon) ist.
Beachten Sie, dass der vorgeschlagene Ersatz an dem Punkt stattfindet, an dem die Ausnahme kurz davor steht, den Frame zu verlassen, d.h. nach dem Verlassen aller except- oder finally-Blöcke, die sie beeinflussen könnten. Die durch Rückgabe aus dem Frame ausgelöste StopIteration wird nicht beeinflusst (der Punkt ist, dass StopIteration bedeutet, dass der Generator „normal“ beendet wurde, d.h. er hat keine Ausnahme ausgelöst).
Ein subtiles Problem ist, was passiert, wenn der Aufrufer, nachdem er die RuntimeError abgefangen hat, die __next__()-Methode des Generatorobjekts erneut aufruft. Die Antwort ist, dass von diesem Zeitpunkt an StopIteration ausgelöst wird – das Verhalten ist dasselbe, als wenn eine andere Ausnahme vom Generator ausgelöst wurde.
Eine weitere logische Konsequenz des Vorschlags: Wenn jemand g.throw(StopIteration) verwendet, um eine StopIteration-Ausnahme in einen Generator zu werfen, wird diese, wenn der Generator sie nicht fängt (was er mit einem try/except um das yield tun könnte), in RuntimeError umgewandelt.
Während der Übergangsphase muss die neue Funktion pro Modul mit folgender Anweisung aktiviert werden:
from __future__ import generator_stop
Jede Generatorfunktion, die unter dem Einfluss dieser Direktive erstellt wird, hat das Flag REPLACE_STOPITERATION auf ihrem Code-Objekt gesetzt, und Generatoren mit gesetztem Flag verhalten sich gemäß diesem Vorschlag. Sobald die Funktion zum Standard wird, kann das Flag entfernt werden; Code sollte Generatoren nicht danach prüfen.
Ein Proof-of-Concept-Patch wurde erstellt, um das Testen zu erleichtern. [4]
Konsequenzen für bestehenden Code
Diese Änderung wird bestehenden Code beeinflussen, der darauf angewiesen ist, dass StopIteration hochblubbert. Die reine Python-Referenzimplementierung von groupby [5] enthält derzeit Kommentare „Exit on StopIteration“, wo erwartet wird, dass die Ausnahme propagiert und dann behandelt wird. Dies wird ungewöhnlich, aber nicht unbekannt sein, und solche Konstrukte werden fehlschlagen. Andere Beispiele gibt es zuhauf, z.B. [6], [7].
(Alyssa Coghlan kommentiert: „Wenn man eine Hilfsfunktion auslagern wollte, die den Generator beendet, müsste man „return yield from helper()“ statt nur „helper()“ schreiben.“)
Es gibt auch Beispiele für Generator Expressions, die auf einer StopIteration basieren, die von der Expression, dem Ziel oder dem Prädikat ausgelöst wird (anstatt vom __next__()-Aufruf, der im eigentlichen for-Loop impliziert ist).
Schreiben von rückwärts- und vorwärtskompatiblem Code
Mit Ausnahme von Hacks, die StopIteration auslösen, um eine Generator Expression zu beenden, ist es einfach, Code zu schreiben, der sowohl unter älteren Python-Versionen als auch unter der neuen Semantik gut funktioniert.
Dies geschieht, indem jene Stellen im Generator-Körper, an denen eine StopIteration erwartet wird (z.B. nackte next()-Aufrufe oder in einigen Fällen Hilfsfunktionen, von denen erwartet wird, dass sie StopIteration auslösen), in ein try/except-Konstrukt eingeschlossen werden, das zurückkehrt, wenn StopIteration ausgelöst wird. Das try/except-Konstrukt sollte sich direkt in der Generatorfunktion befinden; dies in einer Hilfsfunktion zu tun, die selbst kein Generator ist, funktioniert nicht. Wenn raise StopIteration direkt in einem Generator auftritt, ersetzen Sie es einfach durch return.
Beispiele für Brüche
Generatoren, die explizit StopIteration auslösen, können im Allgemeinen so geändert werden, dass sie stattdessen einfach zurückkehren. Dies wird mit allen bestehenden Python-Versionen kompatibel sein und wird nicht von __future__ beeinflusst. Hier einige Beispiele aus der Standardbibliothek.
Lib/ipaddress.py
if other == self:
raise StopIteration
Wird zu
if other == self:
return
In einigen Fällen kann dies mit yield from kombiniert werden, um den Code zu vereinfachen, wie z.B. in Lib/difflib.py
if context is None:
while True:
yield next(line_pair_iterator)
Wird zu
if context is None:
yield from line_pair_iterator
return
(Die return ist für eine strikt äquivalente Übersetzung notwendig, obwohl in dieser speziellen Datei kein weiterer Code vorhanden ist und die return weggelassen werden kann.) Zur Kompatibilität mit Python-Versionen vor 3.3 könnte dies mit einer expliziten for-Schleife geschrieben werden.
if context is None:
for line in line_pair_iterator:
yield line
return
Komplexere Iterationsmuster erfordern explizite try/except-Konstrukte. Zum Beispiel ein hypothetischer Parser wie dieser
def parser(f):
while True:
data = next(f)
while True:
line = next(f)
if line == "- end -": break
data += line
yield data
müsste umgeschrieben werden als
def parser(f):
while True:
try:
data = next(f)
while True:
line = next(f)
if line == "- end -": break
data += line
yield data
except StopIteration:
return
oder möglicherweise
def parser(f):
for data in f:
while True:
line = next(f)
if line == "- end -": break
data += line
yield data
Die letztere Form verschleiert die Iteration, indem sie vorgibt, die Datei mit einer for-Schleife zu durchlaufen, aber während des Schleifenkörpers auch weitere Daten aus demselben Iterator abruft. Sie unterscheidet jedoch klar zwischen einer „normalen“ Beendigung (StopIteration anstelle der anfänglichen Zeile) und einer „abnormalen“ Beendigung (fehlendes Auffinden des Endmarkers in der inneren Schleife, was nun RuntimeError auslöst).
Dieser Effekt von StopIteration wurde verwendet, um eine Generator Expression abzuschneiden und eine Art takewhile zu erzeugen.
def stop():
raise StopIteration
print(list(x for x in range(10) if x < 5 or stop()))
# prints [0, 1, 2, 3, 4]
Unter dem aktuellen Vorschlag wird diese Form der nicht-lokalen Ablaufsteuerung nicht unterstützt und müsste in Anweisungsform umgeschrieben werden.
def gen():
for x in range(10):
if x >= 5: return
yield x
print(list(gen()))
# prints [0, 1, 2, 3, 4]
Obwohl dies ein kleiner Funktionsverlust ist, ist es eine Funktionalität, die oft auf Kosten der Lesbarkeit geht, und so wie lambda Einschränkungen im Vergleich zu def hat, so hat auch eine Generator Expression Einschränkungen im Vergleich zu einer Generatorfunktion. In vielen Fällen ist die Transformation in eine vollständige Generatorfunktion trivial und kann die strukturelle Klarheit verbessern.
Erläuterung von Generatoren, Iteratoren und StopIteration
Der Vorschlag ändert nichts am Verhältnis zwischen Generatoren und Iteratoren: Ein Generatorobjekt ist immer noch ein Iterator, und nicht alle Iteratoren sind Generatoren. Generatoren haben zusätzliche Methoden, die Iteratoren nicht haben, wie send und throw. All dies bleibt unverändert. Für Generator-Benutzer ändert sich nichts – nur Autoren von Generatorfunktionen müssen möglicherweise etwas Neues lernen. (Dies schließt Autoren von Generator Expressions ein, die sich auf die frühe Beendigung der Iteration durch eine in einer Bedingung ausgelöste StopIteration verlassen.)
Ein Iterator ist ein Objekt mit einer __next__-Methode. Wie viele andere spezielle Methoden kann sie entweder einen Wert zurückgeben oder eine spezifische Ausnahme auslösen – in diesem Fall StopIteration –, um zu signalisieren, dass sie keinen Wert zurückzugeben hat. Dies ähnelt __getattr__ (kann AttributeError auslösen), __getitem__ (kann KeyError auslösen) usw. Eine Hilfsfunktion für einen Iterator kann geschrieben werden, um demselben Protokoll zu folgen; zum Beispiel:
def helper(x, y):
if x > y: return 1 / (x - y)
raise StopIteration
def __next__(self):
if self.a: return helper(self.b, self.c)
return helper(self.d, self.e)
Beide Formen der Signalgebung werden übertragen: ein zurückgegebener Wert wird zurückgegeben, eine Ausnahme blubbert nach oben. Die Hilfsfunktion wird so geschrieben, dass sie dem Protokoll der aufrufenden Funktion entspricht.
Eine Generatorfunktion ist eine, die einen yield-Ausdruck enthält. Jedes Mal, wenn sie (neu) gestartet wird, kann sie entweder einen Wert erzeugen oder zurückkehren (einschließlich „herunterfallen“). Eine Hilfsfunktion für einen Generator kann ebenfalls geschrieben werden, muss aber auch das Generator-Protokoll befolgen.
def helper(x, y):
if x > y: yield 1 / (x - y)
def gen(self):
if self.a: return (yield from helper(self.b, self.c))
return (yield from helper(self.d, self.e))
In beiden Fällen blubbert jede unerwartete Ausnahme nach oben. Aufgrund der Natur von Generatoren und Iteratoren wird eine unerwartete StopIteration innerhalb eines Generators in RuntimeError umgewandelt, aber darüber hinaus propagieren alle Ausnahmen normal.
Übergangsplan
- Python 3.5: Neue Semantik unter
__future__-Import aktivieren; stille Verwarnung bei Nichtbeachtung, wennStopIterationeinen Generator verlässt, der nicht unter__future__-Import steht. - Python 3.6: Nicht-stille Verwarnung bei Nichtbeachtung.
- Python 3.7: Neue Semantik überall aktivieren.
Alternative Vorschläge
Auslösen von etwas anderem als RuntimeError
Anstelle der generischen RuntimeError könnte es sinnvoll sein, einen neuen Ausnahme-Typ UnexpectedStopIteration auszulösen. Dies hat den Nachteil, dass es implizit dazu ermutigt, ihn abzufangen; die richtige Aktion ist, die ursprüngliche StopIteration abzufangen, nicht die verkettete Ausnahme.
Bereitstellung einer spezifischen Ausnahme zum Auslösen bei Rückgabe
Alyssa (Nick) Coghlan schlug ein Mittel vor, um eine spezifische StopIteration-Instanz für den Generator bereitzustellen; wenn eine andere Instanz von StopIteration ausgelöst wird, ist dies ein Fehler, aber wenn diese spezielle Instanz ausgelöst wird, ist der Generator korrekt beendet. Dieser Untervorschlag wurde zugunsten besserer Optionen zurückgezogen, wird aber zu Referenzzwecken beibehalten.
StopIterationen, die durch Rückgabe ausgelöst werden, offensichtlich machen
Für bestimmte Situationen könnte eine einfachere und vollständig rückwärtskompatible Lösung ausreichend sein: Wenn ein Generator zurückkehrt, wird anstelle des Auslösens von StopIteration eine spezifische Unterklasse von StopIteration (GeneratorReturn) ausgelöst, die dann erkannt werden kann. Wenn es nicht diese Unterklasse ist, handelt es sich um eine entweichende Ausnahme und nicht um eine Rückgabeanweisung.
Die Inspiration für diesen alternativen Vorschlag war Alyssa's Beobachtung [8], dass wenn eine asyncio-Koroutine [9] versehentlich StopIteration auslöst, sie derzeit lautlos abbricht, was dem Entwickler ein schwer zu debuggendes Mysterium darstellen kann. Der Hauptvorschlag wandelt solche Unfälle in klar unterscheidbare RuntimeError-Ausnahmen um, aber wenn dieser abgelehnt wird, würde dieser alternative Vorschlag es asyncio ermöglichen, zwischen einer return-Anweisung und einer versehentlich ausgelösten StopIteration-Ausnahme zu unterscheiden.
Von den drei oben aufgeführten Ergebnissen ändern zwei
- Wenn ein Yield-Punkt erreicht wird, wird der Wert offensichtlich immer noch zurückgegeben.
- Wenn der Frame verlassen wird, wird
GeneratorReturn(anstelle vonStopIteration) ausgelöst. - Wenn eine Instanz von
GeneratorReturnausgelöst würde, würde stattdessen eine Instanz vonStopIterationausgelöst. Jede andere Ausnahme blubbert normal nach oben.
Im dritten Fall hätte die StopIteration den value der ursprünglichen GeneratorReturn und würde die ursprüngliche Ausnahme in ihrem __cause__ referenzieren. Wenn unbehandelt, würde dies die Verkettung von Ausnahmen deutlich zeigen.
Diese Alternative beeinflusst *nicht* die Diskrepanz zwischen Generator Expressions und List Comprehensions, ermöglicht aber generator-bewusstem Code (wie die Module contextlib und asyncio), zuverlässig zwischen dem zweiten und dritten Ergebnis zu unterscheiden.
Sobald jedoch Code existiert, der von dieser Unterscheidung zwischen GeneratorReturn und StopIteration abhängt, wäre ein Generator, der einen anderen Generator aufruft und darauf angewiesen ist, dass die StopIteration des letzteren nach oben blubbert, immer noch potenziell falsch, abhängig von der Verwendung der Unterscheidung zwischen den beiden Ausnahme-Typen.
Konvertierung der Ausnahme innerhalb von next()
Mark Shannon schlug [10] vor, dass das Problem in next() und nicht an der Grenze von Generatorfunktionen gelöst werden könnte. Indem next() StopIteration fängt und stattdessen ValueError auslöst, würde alle unerwartete StopIteration-Propagation verhindert; die Bedenken hinsichtlich der Rückwärtskompatibilität sind jedoch weitaus schwerwiegender als bei dem aktuellen Vorschlag, da jeder next()-Aufruf nun umgeschrieben werden muss, um gegen ValueError anstelle von StopIteration zu schützen – ganz zu schweigen davon, dass es keine Möglichkeit gibt, einen Codeblock zu schreiben, der zuverlässig auf mehreren Python-Versionen funktioniert. (Die Verwendung eines dedizierten Ausnahme-Typs, vielleicht eine Unterklasse von ValueError, würde dies erleichtern; jedoch müsste immer noch jeder Code umgeschrieben werden.)
Beachten Sie, dass der Aufruf von next(it, default) StopIteration abfängt und den angegebenen Standardwert einfügt; diese Funktion ist oft nützlich, um einen try/except-Block zu vermeiden.
Untervorschlag: Dekorator zur expliziten Anforderung des aktuellen Verhaltens
Alyssa Coghlan schlug [11] vor, dass die Situationen, in denen das aktuelle Verhalten gewünscht ist, mithilfe eines Dekorators unterstützt werden könnten.
from itertools import allow_implicit_stop
@allow_implicit_stop
def my_generator():
...
yield next(it)
...
Was semantisch äquivalent wäre zu
def my_generator():
try:
...
yield next(it)
...
except StopIteration
return
aber schneller wäre, da es einfach durch Zulassen der direkten Propagation der StopIteration implementiert werden könnte.
Auch Single-Source-Python-2/3-Code würde in einer 3.7+-Welt profitieren, da Bibliotheken wie Six und Python-Future ihre eigene Version von „allow_implicit_stop“ definieren könnten, die auf die neue eingebaute Funktion in 3.5+ verweist und in anderen Versionen als Identitätsfunktion implementiert wird.
Aufgrund der erforderlichen Implementierungskomplexität, der entstehenden Kompatibilitätsprobleme, der Subtilität des Effekts des Dekorators und der Tatsache, dass er die „Schnelllösungs“-Lösung fördern würde, alle Generatoren einfach mit dem Dekorator zu versehen, anstatt den fraglichen Code ordnungsgemäß zu reparieren, wurde dieser Untervorschlag abgelehnt. [12]
Kritik
Nichtoffizielle und apokryphe Statistiken deuten darauf hin, dass dies selten oder gar nicht vorkommt. [13] Es existiert zwar Code, der auf dem aktuellen Verhalten basiert (z.B. [3], [6], [7]), und es besteht die Sorge, dass dies unnötiger Code-Churn für wenig oder keinen Gewinn wäre.
Steven D’Aprano startete eine informelle Umfrage auf comp.lang.python [14]; zum Zeitpunkt des Schreibens wurden nur zwei Antworten empfangen: die eine war zugunsten einer Änderung von List Comprehensions, um Generator Expressions zu entsprechen (!), die andere war zugunsten des Hauptvorschlags dieses PEPs.
Das bestehende Modell wurde mit den voll akzeptablen Problemen verglichen, die mit jedem anderen Fall verbunden sind, in dem eine Ausnahme eine besondere Bedeutung hat. Zum Beispiel wird eine unerwartete KeyError innerhalb einer __getitem__-Methode als Fehlschlag interpretiert, anstatt sie nach oben blubbern zu lassen. Es gibt jedoch einen Unterschied. Sonder-Methoden verwenden return, um Normalität anzuzeigen, und raise, um Abnormalität zu signalisieren; Generatoren yield, um Daten anzuzeigen, und return, um den abnormalen Zustand zu signalisieren. Dies macht das explizite Auslösen von StopIteration völlig überflüssig und potenziell überraschend. Wenn andere Sonder-Methoden dedizierte Schlüsselwörter hätten, um zwischen ihren Rückgabepfaden zu unterscheiden, könnten auch sie unerwartete Ausnahmen in RuntimeError umwandeln; die Tatsache, dass sie dies nicht können, sollte Generatoren nicht davon abhalten.
Warum nicht alle __next__() Methoden reparieren?
Bei der Implementierung einer regulären __next__()-Methode ist der einzige Weg, das Ende der Iteration anzuzeigen, das Auslösen von StopIteration. Das Abfangen von StopIteration hier und die Umwandlung in RuntimeError würde den Zweck verfehlen. Dies ist eine Erinnerung an den Sonderstatus von Generatorfunktionen: In einer Generatorfunktion ist das Auslösen von StopIteration überflüssig, da die Iteration durch ein einfaches return beendet werden kann.
Referenzen
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0479.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT