PEP 342 – Coroutines via Enhanced Generators
- Autor:
- Guido van Rossum, Phillip J. Eby
- Status:
- Final
- Typ:
- Standards Track
- Erstellt:
- 10-Mai-2005
- Python-Version:
- 2.5
- Post-History:
Einleitung
Dieses PEP schlägt einige Verbesserungen an der API und Syntax von Generatoren vor, um sie als einfache Coroutinen nutzbar zu machen. Es ist im Grunde eine Kombination von Ideen aus diesen beiden PEPs, die als redundant betrachtet werden können, wenn dieses PEP angenommen wird.
- PEP 288, Generatorenattribute und Ausnahmen. Das aktuelle PEP deckt die zweite Hälfte ab, Generator-Ausnahmen (tatsächlich wurde der Methodenname
throw()aus PEP 288 übernommen). PEP 342 ersetzt jedoch Generator-Attribute durch ein Konzept aus einer früheren Überarbeitung von PEP 288, den *Yield-Ausdruck*. - PEP 325, Unterstützung für Ressourcenfreigabe für Generatoren. PEP 342 schließt einige lose Enden in der PEP 325-Spezifikation ab, um sie für die tatsächliche Implementierung geeignet zu machen.
Motivation
Coroutinen sind eine natürliche Art, viele Algorithmen auszudrücken, wie Simulationen, Spiele, asynchrone I/O und andere Formen ereignisgesteuerter Programmierung oder kooperativer Multitasking. Pythons Generatorfunktionen sind fast Coroutinen – aber eben nicht ganz –, da sie die Ausführung pausieren lassen, um einen Wert zu erzeugen, aber keine Werte oder Ausnahmen bereitstellen, die bei Wiederaufnahme der Ausführung übergeben werden können. Sie erlauben auch keine Ausführungspausen innerhalb des try-Teils von try/finally-Blöcken und machen es daher schwierig für eine abgebrochene Coroutine, sich selbst zu bereinigen.
Außerdem können Generatoren die Kontrolle nicht abgeben, während andere Funktionen ausgeführt werden, es sei denn, diese Funktionen sind selbst als Generatoren ausgedrückt und der äußere Generator ist so geschrieben, dass er als Reaktion auf von den inneren Generatoren erzeugte Werte liefert. Dies erschwert die Implementierung selbst relativ einfacher Anwendungsfälle wie asynchroner Kommunikation, da das Aufrufen von Funktionen entweder erfordert, dass der Generator *blockiert* (d. h. keine Kontrolle abgeben kann) oder es muss viel Boilerplate-Schleifencode um jeden benötigten Funktionsaufruf herum hinzugefügt werden.
Wenn es jedoch möglich wäre, Werte oder Ausnahmen *in* einen Generator an der Stelle zu übergeben, an der er angehalten wurde, könnte ein einfacher Coroutinen-Scheduler oder eine *Trampolin-Funktion* Coroutinen erlauben, sich gegenseitig aufzurufen, ohne zu blockieren – ein enormer Vorteil für asynchrone Anwendungen. Solche Anwendungen könnten dann Coroutinen schreiben, um nicht-blockierende Socket-I/O durch Abgabe der Kontrolle an einen I/O-Scheduler durchzuführen, bis Daten gesendet wurden oder verfügbar sind. In der Zwischenzeit würde der Code, der die I/O durchführt, einfach so etwas tun:
data = (yield nonblocking_read(my_socket, nbytes))
um die Ausführung zu pausieren, bis die nonblocking_read()-Coroutine einen Wert erzeugt hat.
Mit anderen Worten, mit einigen relativ geringfügigen Erweiterungen der Sprache und der Implementierung des Generator-Iterator-Typs wird Python in der Lage sein, asynchrone Operationen durchzuführen, ohne die gesamte Anwendung als Reihe von Callbacks schreiben zu müssen und ohne die Verwendung von ressourcenintensiven Threads für Programme zu benötigen, die Hunderte oder sogar Tausende von kooperativ multitaskingfähigen Pseudothreads benötigen. Somit werden diese Erweiterungen dem Standard-Python viele der Vorteile des Stackless Python-Forks bieten, ohne wesentliche Änderungen am CPython-Kern oder seinen APIs zu erfordern. Darüber hinaus sollten diese Erweiterungen von jeder Python-Implementierung (wie Jython), die bereits Generatoren unterstützt, leicht implementierbar sein.
Zusammenfassung der Spezifikation
Durch Hinzufügen einiger einfacher Methoden zum Generator-Iterator-Typ und mit zwei geringfügigen Syntaxanpassungen werden Python-Entwickler in der Lage sein, Generatorfunktionen zur Implementierung von Coroutinen und anderen Formen kooperativer Multitasking zu verwenden. Diese Methoden und Anpassungen sind:
- Definieren Sie
yieldals Ausdruck statt als Anweisung neu. Die aktuelle yield-Anweisung würde zu einem yield-Ausdruck, dessen Wert verworfen wird. Der Wert eines yield-Ausdrucks istNone, wenn der Generator durch einen normalennext()-Aufruf wieder aufgenommen wird. - Fügen Sie eine neue
send()-Methode für Generator-Iteratoren hinzu, die den Generator wieder aufnimmt und einen Wert *sendet*, der zum Ergebnis des aktuellen Yield-Ausdrucks wird. Diesend()-Methode gibt den nächsten vom Generator gelieferten Wert zurück oder löstStopIterationaus, wenn der Generator ohne Lieferung eines weiteren Wertes beendet wird. - Fügen Sie eine neue
throw()-Methode für Generator-Iteratoren hinzu, die eine Ausnahme an der Stelle auslöst, an der der Generator angehalten wurde, und die den nächsten vom Generator gelieferten Wert zurückgibt, wobeiStopIterationausgelöst wird, wenn der Generator ohne Lieferung eines weiteren Wertes beendet wird. (Wenn der Generator die übergebene Ausnahme nicht abfängt oder eine andere Ausnahme auslöst, breitet sich diese Ausnahme auf den Aufrufer aus.) - Fügen Sie eine
close()-Methode für Generator-Iteratoren hinzu, dieGeneratorExitan der Stelle auslöst, an der der Generator angehalten wurde. Wenn der Generator dannStopIteration(durch normale Beendigung oder weil er bereits geschlossen ist) oderGeneratorExit(durch Nicht-Abfangen der Ausnahme) auslöst, gibtclose()an seinen Aufrufer zurück. Wenn der Generator einen Wert liefert, wird einRuntimeErrorausgelöst. Wenn der Generator eine andere Ausnahme auslöst, wird diese auf den Aufrufer übertragen.close()tut nichts, wenn der Generator bereits aufgrund einer Ausnahme oder normalen Beendigung beendet wurde. - Stellen Sie die Unterstützung sicher, dass
close()aufgerufen wird, wenn ein Generator-Iterator durch die Müllabfuhr gesammelt wird. - Erlauben Sie
yieldintry/finally-Blöcken, da die Müllabfuhr oder ein expliziterclose()-Aufruf nun dazu führen würde, dass diefinally-Klausel ausgeführt wird.
Ein Prototyp-Patch, der all diese Änderungen am aktuellen Python CVS HEAD implementiert, ist als SourceForge-Patch #1223381 verfügbar (https://bugs.python.org/issue1223381).
Spezifikation: Senden von Werten in Generatoren
Neue Generator-Methode: send(value)
Eine neue Methode für Generator-Iteratoren wird vorgeschlagen, namens send(). Sie nimmt genau ein Argument entgegen, nämlich den Wert, der in den Generator *gesendet* werden soll. Der Aufruf von send(None) ist exakt äquivalent zum Aufruf der next()-Methode eines Generators. Der Aufruf von send() mit einem anderen Wert ist derselbe, außer dass der vom aktuellen Yield-Ausdruck des Generators erzeugte Wert anders sein wird.
Da Generator-Iteratoren die Ausführung am Anfang des Funktionskörpers des Generators beginnen, gibt es keinen Yield-Ausdruck, um einen Wert zu empfangen, wenn der Generator gerade erstellt wurde. Daher ist der Aufruf von send() mit einem Nicht- None-Argument verboten, wenn der Generator-Iterator gerade gestartet wurde, und eine TypeError wird ausgelöst, wenn dies geschieht (vermutlich aufgrund eines Logikfehlers irgendeiner Art). Daher müssen Sie, bevor Sie mit einer Coroutine kommunizieren können, zuerst next() oder send(None) aufrufen, um ihre Ausführung bis zum ersten Yield-Ausdruck fortzusetzen.
Wie bei der next()-Methode gibt die send()-Methode den vom Generator-Iterator gelieferten nächsten Wert zurück oder löst StopIteration aus, wenn der Generator normal beendet wird oder bereits beendet wurde. Wenn der Generator eine nicht abgefangene Ausnahme auslöst, wird diese an den Aufrufer von send() weitergegeben.
Neue Syntax: Yield-Ausdrücke
Die yield-Anweisung wird auf der rechten Seite einer Zuweisung erlaubt sein; in diesem Fall wird sie als Yield-Ausdruck bezeichnet. Der Wert dieses Yield-Ausdrucks ist None, es sei denn, send() wurde mit einem Nicht- None-Argument aufgerufen; siehe unten.
Ein Yield-Ausdruck muss immer in Klammern gesetzt werden, es sei denn, er tritt als oberster Ausdruck auf der rechten Seite einer Zuweisung auf. Also
x = yield 42
x = yield
x = 12 + (yield 42)
x = 12 + (yield)
foo(yield 42)
foo(yield)
sind alle legal, aber
x = 12 + yield 42
x = 12 + yield
foo(yield 42, 12)
foo(yield, 12)
sind alle illegal. (Einige der Randfälle werden durch die aktuelle Legalität von yield 12, 42 motiviert.)
Beachten Sie, dass eine yield-Anweisung oder ein yield-Ausdruck ohne Ausdruck jetzt legal ist. Das ist sinnvoll: Wenn der Informationsfluss im next()-Aufruf umgekehrt wird, sollte es möglich sein, zu liefern, ohne einen expliziten Wert zu übergeben (yield ist natürlich äquivalent zu yield None).
Wenn send(value) aufgerufen wird, gibt der Yield-Ausdruck, den es wieder aufnimmt, den übergebenen Wert zurück. Wenn next() aufgerufen wird, gibt der wieder aufgenommene Yield-Ausdruck None zurück. Wenn der Yield-Ausdruck eine yield-Anweisung ist, wird dieser zurückgegebene Wert ignoriert, ähnlich wie der von einem Funktionsaufruf als Anweisung zurückgegebene Wert ignoriert wird.
Im Wesentlichen ist ein Yield-Ausdruck wie ein invertierter Funktionsaufruf; das Argument für yield wird tatsächlich aus der aktuell ausgeführten Funktion *zurückgegeben* (geliefert), und der *Rückgabewert* von yield ist das Argument, das über send() übergeben wird.
Hinweis: Die syntaktischen Erweiterungen für yield machen seine Verwendung sehr ähnlich wie in Ruby. Dies ist beabsichtigt. Beachten Sie, dass in Python der Block einen Wert mit send(EXPR) an den Generator übergibt und nicht mit return EXPR, und der zugrunde liegende Mechanismus, durch den die Kontrolle zwischen dem Generator und dem Block übergeben wird, ist völlig anders. Blöcke in Python werden nicht in Thunks kompiliert; stattdessen suspendiert yield die Ausführung des Generator-Frames. Einige Randfälle funktionieren anders; in Python können Sie den Block nicht für spätere Verwendung speichern und Sie können nicht testen, ob ein Block vorhanden ist oder nicht. (XXX - dieser Teil über Blöcke scheint hier fehl am Platz, vielleicht kann Guido das zur Klärung bearbeiten.)
Spezifikation: Ausnahmen und Bereinigung
Sei ein Generatorobjekt der Iterator, der durch Aufrufen einer Generatorfunktion erzeugt wird. Im Folgenden bezieht sich *g* immer auf ein Generatorobjekt.
Neue Syntax: yield erlaubt in try-finally
Die Syntax für Generatorfunktionen wird erweitert, um eine yield-Anweisung innerhalb einer try-finally-Anweisung zu erlauben.
Neue Generator-Methode: throw(type, value=None, traceback=None)
g.throw(type, value, traceback) verursacht, dass die angegebene Ausnahme an der Stelle ausgelöst wird, an der der Generator *g* gerade angehalten ist (d. h. an einer yield-Anweisung oder am Anfang seines Funktionskörpers, wenn next() noch nicht aufgerufen wurde). Wenn der Generator die Ausnahme abfängt und einen weiteren Wert liefert, ist dies der Rückgabewert von g.throw(). Wenn er die Ausnahme nicht abfängt, scheint throw() dieselbe Ausnahme auszulösen, die ihm übergeben wurde (sie *fällt durch*). Wenn der Generator eine andere Ausnahme auslöst (einschließlich der StopIteration, die beim Rücksprung ausgelöst wird), wird diese Ausnahme durch den Aufruf throw() ausgelöst. Zusammenfassend verhält sich throw() wie next() oder send(), außer dass es an der Halteposition eine Ausnahme auslöst. Wenn sich der Generator bereits im geschlossenen Zustand befindet, löst throw() einfach die ihm übergebene Ausnahme aus, ohne Code des Generators auszuführen.
Die Auswirkung des Auslösens der Ausnahme ist genau so, als ob die Anweisung
raise type, value, traceback
am Haltepunkt ausgeführt worden wäre. Das Typ-Argument darf nicht None sein, und Typ und Wert müssen kompatibel sein. Wenn der Wert keine Instanz des Typs ist, wird eine neue Ausnahminstanz unter Verwendung des Wertes erstellt, gemäß denselben Regeln, die die raise-Anweisung zum Erstellen einer Ausnahminstanz verwendet. Der Traceback, falls vorhanden, muss ein gültiges Python-Traceback-Objekt sein, andernfalls tritt eine TypeError auf.
Hinweis: Der Name der throw()-Methode wurde aus mehreren Gründen gewählt. Raise ist ein Schlüsselwort und kann daher nicht als Methodenname verwendet werden. Im Gegensatz zu raise (das sofort eine Ausnahme vom aktuellen Ausführungspunkt auslöst), nimmt throw() zuerst den Generator wieder auf und löst dann die Ausnahme aus. Das Wort *throw* legt nahe, die Ausnahme an einen anderen Ort zu legen, und ist bereits mit Ausnahmen in anderen Sprachen verbunden.
Alternative Methodennamen wurden erwogen: resolve(), signal(), genraise(), raiseinto() und flush(). Keiner dieser Namen scheint so gut zu passen wie throw().
Neue Standard-Ausnahme: GeneratorExit
Eine neue Standard-Ausnahme wird definiert, GeneratorExit, die von Exception erbt. Ein Generator sollte dies handhaben, indem er sie erneut auslöst (oder sie einfach nicht abfängt) oder indem er StopIteration auslöst.
Neue Generator-Methode: close()
g.close() wird durch den folgenden Pseudocode definiert:
def close(self):
try:
self.throw(GeneratorExit)
except (GeneratorExit, StopIteration):
pass
else:
raise RuntimeError("generator ignored GeneratorExit")
# Other exceptions are not caught
Neue Generator-Methode: __del__()
g.__del__() ist ein Wrapper für g.close(). Dies wird aufgerufen, wenn das Generatorobjekt durch die Müllabfuhr gesammelt wird (in CPython, wenn seine Referenzanzahl auf Null fällt). Wenn close() eine Ausnahme auslöst, wird ein Traceback für die Ausnahme nach sys.stderr ausgegeben und weiter ignoriert; er wird nicht an die Stelle weitergegeben, die die Müllabfuhr ausgelöst hat. Dies entspricht der Behandlung von Ausnahmen in __del__()-Methoden bei Klasseninstanzen.
Wenn das Generatorobjekt an einem Zyklus beteiligt ist, wird g.__del__() möglicherweise nicht aufgerufen. Dies ist das Verhalten des aktuellen Garbage Collectors von CPython. Der Grund für diese Einschränkung ist, dass der GC-Code einen Zyklus an einem beliebigen Punkt *brechen* muss, um ihn zu sammeln, und ab dann darf kein Python-Code die Objekte sehen, die den Zyklus gebildet haben, da sie sich in einem ungültigen Zustand befinden können. Objekte, die von einem Zyklus *hängen*, unterliegen dieser Einschränkung nicht.
Beachten Sie, dass es unwahrscheinlich ist, dass ein Generatorobjekt praktisch an einem Zyklus beteiligt ist. Das Speichern eines Generatorobjekts in einer globalen Variable erzeugt jedoch einen Zyklus über den f_globals-Zeiger des Generator-Frames. Eine andere Möglichkeit, einen Zyklus zu erstellen, wäre, eine Referenz auf das Generatorobjekt in einer Datenstruktur zu speichern, die dem Generator als Argument übergeben wird (z. B. wenn ein Objekt eine Methode hat, die ein Generator ist, und eine Referenz auf einen laufenden Iterator behält, der von dieser Methode erstellt wurde). Keine dieser Fälle ist angesichts der typischen Muster der Generatorverwendung sehr wahrscheinlich.
Außerdem sollte in der CPython-Implementierung dieses PEP der Frame-Objekt, der vom Generator verwendet wird, immer dann freigegeben werden, wenn seine Ausführung aufgrund eines Fehlers oder eines normalen Beendigung beendet wird. Dies stellt sicher, dass Generatoren, die nicht wieder aufgenommen werden können, nicht Teil eines nicht sammelbaren Referenzzyklus bleiben. Dies ermöglicht es anderem Code, close() in einem try/finally oder with-Block (gemäß PEP 343) zu verwenden, um sicherzustellen, dass ein bestimmter Generator ordnungsgemäß finalisiert wird.
Optionale Erweiterungen
Die erweiterte continue-Anweisung
Ein früherer Entwurf dieses PEPs schlug eine neue continue EXPR-Syntax für die Verwendung in for-Schleifen vor (übernommen aus PEP 340), die den Wert von *EXPR* in den durchlaufenen Iterator übergeben würde. Dieses Feature wurde vorerst zurückgezogen, da der Umfang dieses PEPs auf die Übergabe von Werten an Generator-Iteratoren und nicht an andere Arten von Iteratoren beschränkt wurde. Außerdem wurde von einigen Mitgliedern der Python-Dev-Liste empfunden, dass das Hinzufügen neuer Syntax für diese spezielle Funktion bestenfalls verfrüht wäre.
Offene Fragen
Diskussionen auf python-dev haben einige offene Punkte aufgedeckt. Ich liste sie hier mit meiner bevorzugten Auflösung und ihrer Begründung auf. Das PEP in seiner jetzigen Form spiegelt diese bevorzugte Auflösung wider.
- Welche Ausnahme sollte von
close()ausgelöst werden, wenn der Generator als Reaktion auf die AusnahmeGeneratorExiteinen weiteren Wert liefert?Ich habe ursprünglich
TypeErrorgewählt, weil sie grobes Fehlverhalten der Generatorfunktion darstellt, das durch Änderung des Codes behoben werden sollte. Aber die Dekorator-Klassewith_templatein PEP 343 verwendetRuntimeErrorfür ähnliche Vergehen. Arguably sollten sie alle dieselbe Ausnahme verwenden. Ich möchte keine neue Ausnahmeklasse nur für diesen Zweck einführen, da es keine Ausnahme ist, die ich Leute abfangen lassen möchte: Ich möchte, dass sie zu einem Traceback wird, der vom Programmierer gesehen wird, der dann den Code korrigiert. Daher glaube ich jetzt, dass sie beideRuntimeErrorauslösen sollten. Es gibt einige Präzedenzfälle dafür: sie wird vom Kern-Python-Code in Situationen ausgelöst, in denen endlose Rekursion erkannt wird, und für uninitialisierte Objekte (und für eine Vielzahl von verschiedenen Bedingungen). - Oren Tirosh hat vorgeschlagen, die
send()-Methode infeed()umzubenennen, um die Kompatibilität mit der *Consumer-Schnittstelle* zu gewährleisten (siehe http://effbot.org/zone/consumer.htm für die Spezifikation).Bei näherer Betrachtung der Consumer-Schnittstelle scheint es jedoch, dass die gewünschten Semantiken für
feed()von denen fürsend()abweichen, dasend()bei einem gerade gestarteten Generator nicht sinnvoll aufgerufen werden kann. Außerdem beinhaltet die derzeit definierte Consumer-Schnittstelle keine Handhabung fürStopIteration.Daher scheint es wahrscheinlich nützlicher zu sein, einen einfachen Dekorator zu erstellen, der eine Generatorfunktion umschließt, um sie an die Consumer-Schnittstelle anzupassen. Zum Beispiel könnte sie den Generator mit einem anfänglichen
next()-Aufruf *aufwärmen*, StopIteration abfangen und vielleicht sogarreset()bereitstellen, indem die Generatorfunktion erneut aufgerufen wird.
Beispiele
- Ein einfacher *Consumer*-Dekorator, der eine Generatorfunktion automatisch bis zu ihrem ersten Yield-Punkt fortsetzt, wenn sie initial aufgerufen wird.
def consumer(func): def wrapper(*args,**kw): gen = func(*args, **kw) gen.next() return gen wrapper.__name__ = func.__name__ wrapper.__dict__ = func.__dict__ wrapper.__doc__ = func.__doc__ return wrapper
- Ein Beispiel für die Verwendung des *Consumer*-Dekorators zur Erstellung eines *Reverse Generators*, der Bilder empfängt und Thumbnail-Seiten erstellt, die an einen anderen Consumer gesendet werden. Funktionen wie diese können verkettet werden, um effiziente Verarbeitungspipelines von *Consumern* zu bilden, von denen jeder einen komplexen internen Zustand haben kann.
@consumer def thumbnail_pager(pagesize, thumbsize, destination): while True: page = new_image(pagesize) rows, columns = pagesize / thumbsize pending = False try: for row in xrange(rows): for column in xrange(columns): thumb = create_thumbnail((yield), thumbsize) page.write( thumb, col*thumbsize.x, row*thumbsize.y ) pending = True except GeneratorExit: # close() was called, so flush any pending output if pending: destination.send(page) # then close the downstream consumer, and exit destination.close() return else: # we finished a page full of thumbnails, so send it # downstream and keep on looping destination.send(page) @consumer def jpeg_writer(dirname): fileno = 1 while True: filename = os.path.join(dirname,"page%04d.jpg" % fileno) write_jpeg((yield), filename) fileno += 1 # Put them together to make a function that makes thumbnail # pages from a list of images and other parameters. # def write_thumbnails(pagesize, thumbsize, images, output_dir): pipeline = thumbnail_pager( pagesize, thumbsize, jpeg_writer(output_dir) ) for image in images: pipeline.send(image) pipeline.close()
- Ein einfacher *Coroutinen-Scheduler* oder *Trampolin*, der es Coroutinen ermöglicht, andere Coroutinen aufzurufen, indem die Coroutine, die sie aufrufen möchten, als Wert geliefert wird. Jeder Nicht-Generator-Wert, der von einer Coroutine geliefert wird, wird an die Coroutine zurückgegeben, die diejenige aufgerufen hat, die den Wert geliefert hat. Ebenso wird, wenn eine Coroutine eine Ausnahme auslöst, die Ausnahme an ihren *Aufrufer* weitergegeben. Tatsächlich emuliert dieses Beispiel einfache Tasklets, wie sie in Stackless Python verwendet werden, solange Sie einen Yield-Ausdruck verwenden, um Routinen aufzurufen, die ansonsten *blockieren* würden. Dies ist nur ein sehr einfaches Beispiel, und weitaus ausgefeiltere Scheduler sind möglich. (Zum Beispiel implementieren das bestehende GTasklet-Framework für Python (http://www.gnome.org/~gjc/gtasklet/gtasklets.html) und das peak.events-Framework (http://peak.telecommunity.com/) bereits ähnliche Scheduling-Fähigkeiten, müssen aber derzeit umständliche Workarounds für die Unfähigkeit verwenden, Werte oder Ausnahmen in Generatoren zu übergeben.)
import collections class Trampoline: """Manage communications between coroutines""" running = False def __init__(self): self.queue = collections.deque() def add(self, coroutine): """Request that a coroutine be executed""" self.schedule(coroutine) def run(self): result = None self.running = True try: while self.running and self.queue: func = self.queue.popleft() result = func() return result finally: self.running = False def stop(self): self.running = False def schedule(self, coroutine, stack=(), val=None, *exc): def resume(): value = val try: if exc: value = coroutine.throw(value,*exc) else: value = coroutine.send(value) except: if stack: # send the error back to the "caller" self.schedule( stack[0], stack[1], *sys.exc_info() ) else: # Nothing left in this pseudothread to # handle it, let it propagate to the # run loop raise if isinstance(value, types.GeneratorType): # Yielded to a specific coroutine, push the # current one on the stack, and call the new # one with no args self.schedule(value, (coroutine,stack)) elif stack: # Yielded a result, pop the stack and send the # value to the caller self.schedule(stack[0], stack[1], value) # else: this pseudothread has ended self.queue.append(resume)
- Ein einfacher *Echo*-Server und Code, um ihn mithilfe eines Trampolins auszuführen (setzt das Vorhandensein von
nonblocking_read,nonblocking_writeund anderen I/O-Coroutinen voraus, die z. B.ConnectionLostauslösen, wenn die Verbindung geschlossen wird).# coroutine function that echos data back on a connected # socket # def echo_handler(sock): while True: try: data = yield nonblocking_read(sock) yield nonblocking_write(sock, data) except ConnectionLost: pass # exit normally if connection lost # coroutine function that listens for connections on a # socket, and then launches a service "handler" coroutine # to service the connection # def listen_on(trampoline, sock, handler): while True: # get the next incoming connection connected_socket = yield nonblocking_accept(sock) # start another coroutine to handle the connection trampoline.add( handler(connected_socket) ) # Create a scheduler to manage all our coroutines t = Trampoline() # Create a coroutine instance to run the echo_handler on # incoming connections # server = listen_on( t, listening_socket("localhost","echo"), echo_handler ) # Add the coroutine to the scheduler t.add(server) # loop forever, accepting connections and servicing them # "in parallel" # t.run()
Referenzimplementierung
Ein Prototyp-Patch, der alle in diesem PEP beschriebenen Features implementiert, ist als SourceForge-Patch #1223381 verfügbar (https://bugs.python.org/issue1223381).
Dieser Patch wurde am 01.-02. August 2005 in CVS committet.
Danksagungen
Raymond Hettinger (PEP 288) und Samuele Pedroni (PEP 325) schlugen zuerst formell die Ideen der Kommunikation von Werten oder Ausnahmen in Generatoren und die Fähigkeit, Generatoren zu *schließen*, vor. Timothy Delaney schlug den Titel dieses PEP vor, und Steven Bethard half bei der Bearbeitung einer früheren Version. Siehe auch den Abschnitt "Danksagungen" von PEP 340.
Referenzen
TBD.
Urheberrecht
Dieses Dokument wurde gemeinfrei erklärt.
Quelle: https://github.com/python/peps/blob/main/peps/pep-0342.rst
Zuletzt geändert: 2025-02-01 08:59:27 GMT