2012-07-14 7 views
41

PEP 342 (Coroutines via Enhanced Generators) hinzugefügt eine throw() Methode, um Objekte zu generieren, die der Aufrufer eine Ausnahme innerhalb des Generators auslösen kann (als ob es durch den Ausdruck yield ausgelöst wurde).Für was ist generator.throw() gut?

Ich frage mich, was die Anwendungsfälle für diese Funktion sind.

+1

Kontext: Ich arbeite gerade an einer Generator/Coroutine-Implementierung in PHP und frage mich, ob ich die 'throw()' -Funktionalität mit einbeziehen soll oder nicht. – NikiC

+3

Willst du Generatoren oder Coroutinen? Während Python die beiden zusammenfasst, und Sie die erstere aus letzterem aufbauen können, sind sie anders (wie in einer ganz anderen Liga). – delnan

Antwort

47

Angenommen, ich benutze einen Generator, um Informationen in eine Datenbank einzufügen; Ich verwende dies, um vom Netzwerk empfangene Informationen zu speichern, und indem ich einen Generator verwende, kann ich dies effizient tun, wann immer ich tatsächlich Daten erhalte, und sonst etwas anderes tun.

Also, mein Generator zunächst eine Datenbankverbindung öffnet, und jedes Mal, wenn es etwas zu senden, wird es eine Zeile hinzufügen:

def add_to_database(connection_string): 
    db = mydatabaselibrary.connect(connection_string) 
    cursor = db.cursor() 
    while True: 
     row = yield 
     cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row) 

Das ist alles schön und gut ist; jedes Mal, wenn ich meine Daten .send() einfüge, wird eine Zeile eingefügt.

Aber was, wenn meine Datenbank transaktional ist? Wie signalisiere ich diesen Generator, wenn die Daten in die Datenbank übertragen werden sollen? Und wann soll die Transaktion abgebrochen werden? Außerdem hält es eine offene Verbindung zur Datenbank aufrecht, vielleicht möchte ich manchmal, dass es diese Verbindung schließt, um Ressourcen zurückzufordern.

Hier kommt die .throw() Methode ins Spiel; mit .throw() kann ich Ausnahmen in diesem Verfahren erhöht bestimmte Umstände zu signalisieren:

def add_to_database(connection_string): 
    db = mydatabaselibrary.connect(connection_string) 
    cursor = db.cursor() 
    try: 
     while True: 
      try: 
       row = yield 
       cursor.execute('INSERT INTO mytable VALUES(?, ?, ?)', row) 
      except CommitException: 
       cursor.execute('COMMIT') 
      except AbortException: 
       cursor.execute('ABORT') 
    finally: 
     cursor.execute('ABORT') 
     db.close() 

Die .close() Verfahren an einem Generator funktionieren im Wesentlichen die gleiche Sache; Es verwendet die GeneratorExit-Ausnahme kombiniert mit .throw(), um einen laufenden Generator zu schließen.

All dies ist eine wichtige Untermauerung davon, wie coroutines arbeiten; Koroutinen sind im wesentlichen Generatoren, zusammen mit einer zusätzlichen Syntax, um das Schreiben einer Koroutine einfacher und klarer zu machen. Aber unter der Haube sind sie immer noch auf demselben Ertrag gebaut und senden. Und wenn Sie mehrere Coroutinen parallel ausführen, benötigen Sie eine Möglichkeit, diese Korutinen sauber zu verlassen, wenn einer von ihnen fehlgeschlagen ist, um nur ein Beispiel zu nennen.

+6

Danke für Ihre Antwort. Dies ist definitiv ein interessanter Anwendungsfall. Aber ich frage mich, ob dies als Ausnahme-Missbrauch eingestuft werden könnte. Commit und Abort sind keine außergewöhnlichen Bedingungen, sondern Teil des üblichen Verhaltens. Ausnahmen werden also grundsätzlich als Mittel zur Änderung des Kontrollflusses verwendet. – NikiC

+2

@NikiC Ihr Punkt ist für synchrone Programmierung gültig, aber Sie müssen dies in der Welt der asynch Programmierung sehen. Stellen Sie sich vor, der obige try-Block wäre viel größer (nennen Sie den Code in try, den allgemeinen Anwendungsfall) und vielleicht sogar noch ein paar weitere Yield-Anweisungen, so dass der Generator während seines allgemeinen Anwendungsfalls ein- und ausgeht. Die .throw() -Methode ermöglicht es uns, "auszubrechen", um spezielle Ausnahmen zu behandeln. Wenn Sie mit Interrupt-Handlern vertraut sind, können Sie sich das so vorstellen. Auf diese Weise können wir den Ablauf unterbrechen, um spezielle (wenn nicht kritische) Operationen auszuführen, egal wo im Anwendungsfall –

+3

@NikiC Es ist nichts Falsches daran, Ausnahmen für den Kontrollfluss zu verwenden. – Marcin

9

Meiner Meinung nach ist die throw() Methode aus vielen Gründen nützlich.

  1. Symmetrie: Es gibt keinen guten Grund, für die ein außergewöhnlich guten Zustand sollte nur in dem Anrufer und nicht auch in der Generatorfunktion behandelt werden. (Nehmen wir an, ein Generator, der Werte aus einer Datenbank liest, gibt einen schlechten Wert zurück und nimmt an, dass nur der Anrufer weiß, dass der Wert schlecht ist. Mit der Methode throw() kann der Anrufer dem Generator signalisieren, dass eine anormale Situation korrigiert werden muss .) Wenn der Generator eine vom Anrufer abgefangene Ausnahme auslösen kann, sollte auch die Umkehrung möglich sein.

  2. Flexibilität: Eine Generatorfunktion kann mehr als eine yield Anweisung haben, und der Aufrufer kann den internen Status des Generators nicht kennen. Durch das Auslösen von Ausnahmen ist es möglich, den Generator in einen bekannten Zustand zurückzusetzen oder eine ausgeklügeltere Flusssteuerung zu implementieren, die mit next(), send(), close() allein viel komplizierter wäre.

Vorstellung für Anwendungsfälle kann irreführend sein: Für jeden Anwendungsfall Sie ein Gegenbeispiel, ohne die Notwendigkeit für ein throw() Verfahren produzieren könnten, und die Diskussion weiterhin für immer ...

3

Ein Anwendungsfall ist Informationen über den internen Status eines Generators in der Stack-Ablaufverfolgung enthalten, wenn eine Ausnahme auftritt - Informationen, die andernfalls für den Aufrufer nicht sichtbar wären.

Zum Beispiel, sagen wir haben einen Generator wie folgt aus, wo der innere Zustand wir die aktuelle Indexnummer des Generators wollen:

def gen_items(): 
    for i, item in enumerate(["", "foo", "", "foo", "bad"]): 
     if not item: 
      continue 
     try: 
      yield item 
     except Exception: 
      raise Exception("error during index: %d" % i) 

Der folgende Code nicht ausreicht, um die zusätzliche Ausnahmebehandlung ausgelöst werden:

# Stack trace includes only: "ValueError: bad value" 
for item in gen_items(): 
    if item == "bad": 
     raise ValueError("bad value") 

Allerdings ist der folgende Code den internen Zustand bieten:

# Stack trace also includes: "Exception: error during index: 4" 
gen = item_generator() 
for item in gen: 
    if item == "bad": 
     gen.throw(ValueError, "bad value")