PEP 325 – Unterstützung für die Freigabe von Ressourcen für Generatoren
- Autor:
- Samuele Pedroni <pedronis at python.org>
- Status:
- Abgelehnt
- Typ:
- Standards Track
- Erstellt:
- 25-Aug-2003
- Python-Version:
- 2.4
- Post-History:
Zusammenfassung
Generatoren ermöglichen eine natürliche Kodierung und Abstraktion der Traversierung über Daten. Derzeit sind Generatoren ungeeignet, wenn externe Ressourcen ordnungsgemäß und rechtzeitig freigegeben werden müssen. Das typische Idiom für rechtzeitige Freigabe wird nicht unterstützt, eine `yield`-Anweisung ist in der `try`-Klausel einer `try-finally`-Anweisung innerhalb eines Generators nicht erlaubt. Die Ausführung der `finally`-Klausel kann weder garantiert noch erzwungen werden.
Dieses PEP schlägt vor, dass der eingebaute Generatortyp eine `close`-Methode und eine Zerstörungssemantik implementiert, sodass die Einschränkung bezüglich der Platzierung von `yield` aufgehoben werden kann, um die Anwendbarkeit von Generatoren zu erweitern.
Bekanntmachung
Abgelehnt zugunsten von PEP 342, das im Wesentlichen das gesamte angeforderte Verhalten in einer verfeinerten Form enthält.
Begründung
Python-Generatoren ermöglichen die natürliche Kodierung vieler Szenarien der Datentraversierung. Ihre Instanziierung erzeugt Iteratoren, d.h. First-Class-Objekte, die die Traversierung abstrahieren (mit allen Vorteilen der First-Class-Natur). In dieser Hinsicht sind sie leistungsfähig und bieten einige Vorteile gegenüber dem Ansatz, der Iterator-Methoden verwendet, die einen (Smalltalk-ähnlichen) Block nehmen. Andererseits scheint letzterer Ansatz angesichts der aktuellen Einschränkungen (kein `yield` in einer `try`-Klausel einer `try-finally`-Anweisung innerhalb eines Generators erlaubt) besser geeignet zu sein, nicht nur die Traversierung, sondern auch die Fehlerbehandlung sowie die ordnungsgemäße Erfassung und Freigabe von Ressourcen zu kapseln.
Betrachten wir ein Beispiel (der Einfachheit halber werden Dateien im Lesemodus verwendet)
def all_lines(index_path):
for path in file(index_path, "r"):
for line in file(path.strip(), "r"):
yield line
dies ist kurz und bündig, aber das `try-finally` zur rechtzeitigen Schließung der Dateien kann nicht hinzugefügt werden. (Während anstelle eines Pfads eine Datei, deren Schließung dann in der Verantwortung des Aufrufers liegt, als Argument übergeben werden könnte, gilt dasselbe nicht für die Dateien, die je nach Inhalt des Index geöffnet werden).
Wenn wir eine rechtzeitige Freigabe wünschen, müssen wir die Einfachheit und Direktheit des reinen Generatoransatzes opfern: (z.B.)
class AllLines:
def __init__(self, index_path):
self.index_path = index_path
self.index = None
self.document = None
def __iter__(self):
self.index = file(self.index_path, "r")
for path in self.index:
self.document = file(path.strip(), "r")
for line in self.document:
yield line
self.document.close()
self.document = None
def close(self):
if self.index:
self.index.close()
if self.document:
self.document.close()
zu verwenden als
all_lines = AllLines("index.txt")
try:
for line in all_lines:
...
finally:
all_lines.close()
Die umständlichere Lösung zur Implementierung der rechtzeitigen Freigabe scheint ein wertvoller Hinweis zu sein. Was wir getan haben, ist, unsere Traversierung in einem Objekt (Iterator) mit einer `close`-Methode zu kapseln.
Dieses PEP schlägt vor, dass Generatoren eine solche `close`-Methode mit einer Semantik erhalten sollten, die es ermöglicht, das Beispiel umzuschreiben als
# Today this is not valid Python: yield is not allowed between
# try and finally, and generator type instances support no
# close method.
def all_lines(index_path):
index = file(index_path, "r")
try:
for path in index:
document = file(path.strip(), "r")
try:
for line in document:
yield line
finally:
document.close()
finally:
index.close()
all = all_lines("index.txt")
try:
for line in all:
...
finally:
all.close() # close on generator
Derzeit verbietet PEP 255 `yield` innerhalb einer `try`-Klausel einer `try-finally`-Anweisung, da die Ausführung der `finally`-Klausel nicht wie von der `try-finally`-Semantik gefordert garantiert werden kann.
Die Semantik der vorgeschlagenen `close`-Methode sollte so sein, dass, obwohl die Ausführung der `finally`-Klausel immer noch nicht garantiert werden kann, sie erzwungen werden kann, wenn sie benötigt wird. Insbesondere sollte das Verhalten der `close`-Methode die Ausführung der `finally`-Klauseln innerhalb des Generators auslösen, entweder durch Erzwingen einer Rückgabe im Generator-Frame oder durch Werfen einer Ausnahme darin. In Situationen, die eine rechtzeitige Ressourcenfreigabe erfordern, könnte `close` dann explizit aufgerufen werden.
Die Semantik der Generatorzerstörung sollte andererseits erweitert werden, um eine Best-Effort-Politik für den allgemeinen Fall zu implementieren. Insbesondere sollte die Zerstörung das Verhalten von `close()` aufrufen. Die Best-Effort-Beschränkung ergibt sich aus der Tatsache, dass die Ausführung des Destruktors an sich nicht garantiert ist.
Dies scheint ein vernünftiger Kompromiss zu sein, wobei das resultierende globale Verhalten dem von Dateien und deren Schließung ähnelt.
Mögliche Semantik
Der eingebaute Generatortyp sollte eine implementierte `close`-Methode haben, die dann wie folgt aufgerufen werden kann
gen.close()
wobei gen eine Instanz des eingebauten Generatortyps ist. Die Generatorzerstörung sollte ebenfalls das Verhalten der `close`-Methode aufrufen.
Wenn ein Generator bereits beendet ist, sollte `close` eine No-Op sein.
Andernfalls gibt es zwei alternative Lösungen: Rückgabe- oder Ausnahmesemantik
A - Rückgabesemantik: Der Generator sollte wiederaufgenommen werden, und die Ausführung des Generators sollte so fortgesetzt werden, als sei die Anweisung am Wiedereintrittspunkt eine Rückgabe. Folglich würden die `finally`-Klauseln, die den Wiedereintrittspunkt umgeben, ausgeführt, im Falle eines dann erlaubten `try-yield-finally`-Musters.
Probleme: Ist es wichtig, zwischen erzwungener Beendigung durch `close`, normaler Beendigung, Ausnahme-Propagierung vom Generator oder von ihm aufgerufenem Code unterscheiden zu können? Im Normalfall scheint dies nicht wichtig zu sein, `finally`-Klauseln sollten dazu da sein, in all diesen Fällen gleich zu funktionieren, dennoch könnte diese Semantik eine solche Unterscheidung erschweren.
`except`-Klauseln werden, wie bei einer normalen Rückgabe, nicht ausgeführt. Solche Klauseln in älteren Generatoren erwarten, dass sie für Ausnahmen ausgeführt werden, die vom Generator oder vom von ihm aufgerufenen Code ausgelöst werden. Diese im `close`-Fall nicht auszuführen, scheint korrekt.
B - Ausnahmesemantik: Der Generator sollte wiederaufgenommen werden, und die Ausführung sollte so fortgesetzt werden, als sei eine spezielle Ausnahme (z.B. `CloseGenerator`) am Wiedereintrittspunkt ausgelöst worden. Die `close`-Implementierung sollte diese Ausnahme konsumieren und nicht weiter propagieren.
Probleme: Sollte StopIteration für diesen Zweck wiederverwendet werden? Wahrscheinlich nicht. Wir möchten, dass `close` eine harmlose Operation für ältere Generatoren ist, die Code enthalten könnten, der StopIteration abfängt, um andere Generatoren/Iteratoren zu behandeln.
Im Allgemeinen ist es bei Ausnahmesemantik unklar, was zu tun ist, wenn der Generator nicht terminiert oder wir die spezielle Ausnahme nicht zurückerhalten. Andere, unterschiedliche Ausnahmen sollten wahrscheinlich weitergegeben werden, aber betrachten Sie diesen möglichen älteren Generatorcode
try:
...
yield ...
...
except: # or except Exception:, etc
raise Exception("boom")
Wenn `close` mit dem suspendierten Generator nach dem `yield` aufgerufen wird, würde die `except`-Klausel unsere spezielle Ausnahme abfangen, sodass wir eine andere Ausnahme zurückerhalten, die in diesem Fall vernünftigerweise konsumiert und ignoriert werden sollte, aber im Allgemeinen weitergegeben werden sollte, aber die Trennung dieser Szenarien scheint schwierig.
Der Ausnahmeansatz hat den Vorteil, dass der Generator zwischen Beendigungsszenarien unterscheiden und mehr Kontrolle haben kann. Andererseits scheint eine klare Semantik schwieriger zu definieren zu sein.
Bemerkungen
Wenn dieser Vorschlag angenommen wird, sollte es üblich werden zu dokumentieren, ob ein Generator Ressourcen erwirbt, so dass seine `close`-Methode aufgerufen werden sollte. Wenn ein Generator nicht mehr verwendet wird, sollte das Aufrufen von `close` harmlos sein.
Andererseits sollte im typischen Szenario der Code, der den Generator instanziiert hat, `close` aufrufen, falls dies durch ihn erforderlich ist. Generischer Code, der woanders instanziierte Iteratoren/Generatoren behandelt, sollte normalerweise nicht mit `close`-Aufrufen übersät werden.
Der seltene Fall von Code, der die Eigentümerschaft erworben hat und sich ordnungsgemäß um alle Iteratoren, Generatoren und Ressourcen erwerbenden Generatoren kümmern muss, die rechtzeitig freigegeben werden müssen, ist leicht zu lösen
if hasattr(iterator, 'close'):
iterator.close()
Offene Fragen
Eine endgültige Semantik sollte gewählt werden. Derzeit bevorzugt Guido die Ausnahmesemantik. Wenn der Generator einen Wert anstelle einer Beendigung liefert oder die spezielle Ausnahme zurückgibt, sollte auf der Generatorseite eine spezielle Ausnahme erneut ausgelöst werden.
Es ist immer noch unklar, ob zufällig konvertierte spezielle Ausnahmen (wie in Mögliche Semantik diskutiert) ein Problem darstellen und was dagegen zu tun ist.
Implementierungsfragen sollten untersucht werden.
Alternative Ideen
Die Idee, dass die Einschränkung der `yield`-Platzierung aufgehoben werden sollte und dass die Generatorzerstörung die Ausführung von `finally`-Klauseln auslösen sollte, wurde mehr als einmal vorgeschlagen. Allein kann sie nicht garantieren, dass die rechtzeitige Freigabe von durch einen Generator erworbenen Ressourcen erzwungen werden kann.
PEP 288 schlägt eine allgemeinere Lösung vor, die benutzerdefinierte Ausnahmeübergaben an Generatoren ermöglicht. Der Vorschlag in diesem PEP adressiert das Problem der Ressourcenfreigabe direkter. Würde PEP 288 implementiert, könnte die Ausnahmesemantik für `close` darauf aufbauen. Andererseits müsste PEP 288 einen separaten Fall für die allgemeinere Funktionalität darstellen.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0325.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT