Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

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:


Inhaltsverzeichnis

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:

  1. Definieren Sie yield als Ausdruck statt als Anweisung neu. Die aktuelle yield-Anweisung würde zu einem yield-Ausdruck, dessen Wert verworfen wird. Der Wert eines yield-Ausdrucks ist None, wenn der Generator durch einen normalen next()-Aufruf wieder aufgenommen wird.
  2. 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. Die send()-Methode gibt den nächsten vom Generator gelieferten Wert zurück oder löst StopIteration aus, wenn der Generator ohne Lieferung eines weiteren Wertes beendet wird.
  3. 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, wobei StopIteration ausgelö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.)
  4. Fügen Sie eine close()-Methode für Generator-Iteratoren hinzu, die GeneratorExit an der Stelle auslöst, an der der Generator angehalten wurde. Wenn der Generator dann StopIteration (durch normale Beendigung oder weil er bereits geschlossen ist) oder GeneratorExit (durch Nicht-Abfangen der Ausnahme) auslöst, gibt close() an seinen Aufrufer zurück. Wenn der Generator einen Wert liefert, wird ein RuntimeError ausgelö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.
  5. Stellen Sie die Unterstützung sicher, dass close() aufgerufen wird, wenn ein Generator-Iterator durch die Müllabfuhr gesammelt wird.
  6. Erlauben Sie yield in try/finally-Blöcken, da die Müllabfuhr oder ein expliziter close()-Aufruf nun dazu führen würde, dass die finally-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.

  1. Welche Ausnahme sollte von close() ausgelöst werden, wenn der Generator als Reaktion auf die Ausnahme GeneratorExit einen weiteren Wert liefert?

    Ich habe ursprünglich TypeError gewählt, weil sie grobes Fehlverhalten der Generatorfunktion darstellt, das durch Änderung des Codes behoben werden sollte. Aber die Dekorator-Klasse with_template in PEP 343 verwendet RuntimeError fü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 beide RuntimeError auslö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).

  2. Oren Tirosh hat vorgeschlagen, die send()-Methode in feed() 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ür send() abweichen, da send() bei einem gerade gestarteten Generator nicht sinnvoll aufgerufen werden kann. Außerdem beinhaltet die derzeit definierte Consumer-Schnittstelle keine Handhabung für StopIteration.

    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 sogar reset() bereitstellen, indem die Generatorfunktion erneut aufgerufen wird.

Beispiele

  1. 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
    
  2. 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()
    
  3. 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)
    
  4. Ein einfacher *Echo*-Server und Code, um ihn mithilfe eines Trampolins auszuführen (setzt das Vorhandensein von nonblocking_read, nonblocking_write und anderen I/O-Coroutinen voraus, die z. B. ConnectionLost auslö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.


Quelle: https://github.com/python/peps/blob/main/peps/pep-0342.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT