36 Stimmen

Behandeln Sie Generatorausnahmen in seinem Verbraucher

Dies ist eine Fortsetzung von Behandlung einer in einem Generator geworfenen Ausnahme und behandelt ein allgemeineres Problem.

Ich habe eine Funktion, die Daten in verschiedenen Formaten liest. Alle Formate sind zeilen- oder satzorientiert und für jedes Format gibt es eine dedizierte Parsing-Funktion, die als Generator implementiert ist. Die Hauptlesefunktion erhält also eine Eingabe und einen Generator, der sein jeweiliges Format aus der Eingabe liest und Datensätze an die Hauptfunktion zurückliefert:

def read(stream, parsefunc):
    for record in parsefunc(stream):
        mache_etwas(record)

wobei parsefunc so etwas ist wie:

def parsefunc(stream):
    while not eof(stream):
        rec = read_record(stream)
        mach etwas
        yield rec

Das Problem, dem ich gegenüberstehe, ist, dass obwohl parsefunc eine Ausnahme werfen kann (z.B. beim Lesen aus einem Stream), es keine Ahnung hat, wie damit umzugehen ist. Die Funktion, die für die Behandlung von Ausnahmen verantwortlich ist, ist die Hauptfunktion read. Beachten Sie, dass Ausnahmen auf zeilenweiser Grundlage auftreten, daher sollte der Generator seine Arbeit fortsetzen und Datensätze zurückliefern, auch wenn ein Datensatz fehlschlägt, bis der gesamte Stream erschöpft ist.

In der vorherigen Frage versuchte ich, next(parsefunc) in einen try-Block zu setzen, aber wie sich herausstellte, wird dies nicht funktionieren. Also muss ich ein try-except zur parsefunc selbst hinzufügen und dann irgendwie Ausnahmen an den Verbraucher weitergeben:

def parsefunc(stream):
    while not eof(stream):
        try:
            rec = read_record()
            yield rec
        except Exception as e:
            ?????

Ich bin eher zurückhaltend, dies zu tun, weil

  • es keinen Sinn macht, try in einer Funktion zu verwenden, die nicht dazu gedacht ist, Ausnahmen zu behandeln
  • mir unklar ist, wie Ausnahmen an die verbrauchende Funktion übergeben werden sollen
  • es viele Formate und viele parsefunc's geben wird, möchte ich sie nicht mit zu viel Hilfscode überladen.

Hat jemand Vorschläge für eine bessere Architektur?

Eine Notiz für Googler: Neben der besten Antwort beachten Sie auch senderle's und Jon's Beiträge - sehr kluge und aufschlussreiche Sachen.

2voto

Rick supports Monica Punkte 38114

(Ich habe die andere Frage, die im OP verlinkt war, beantwortet, aber meine Antwort gilt auch für diese Situation)

Ich musste dieses Problem schon ein paar Mal lösen und bin nach einer Suche danach, was andere Leute gemacht haben, auf diese Frage gestoßen.

Eine Option - die wahrscheinlich eine Umstrukturierung der Dinge erfordern wird - wäre einfach, einen Fehlerbehandlungsgenerator zu erstellen und die Ausnahme im Generator zu throwen (zu einem anderen Fehlerbehandlungsgenerator) anstatt es zu raisen.

So könnte die Funktion des Fehlerbehandlungsgenerators aussehen:

def err_handler():
    # ein Generator zur Fehlerverarbeitung
    while True:
        try:
            # Fehler werden bis zu diesem Punkt in der Funktion geworfen
            yield
        except Exception1:
            handle_exc1()
        except Exception2:
            handle_exc2()
        except Exception3:
            handle_exc3()
        except Exception:
            raise

Ein zusätzliches handler-Argument wird der parsefunc-Funktion zur Verfügung gestellt, damit es einen Ort für die Fehler hat:

def parsefunc(stream, handler):
    # das handler-Argument behebt Fehler/Probleme separat
    while not eof(stream):
        try:
            rec = read_record(stream)
            etwas tun
            yield rec
        except Exception as e:
            handler.throw(e)
    handler.close()

Verwenden Sie jetzt einfach fast die originale read-Funktion, aber jetzt mit einem Fehlerbehandler:

def read(stream, parsefunc):
    handler = err_handler()
    for record in parsefunc(stream, handler):
        etwas mit record machen

Dies ist nicht immer die beste Lösung, aber es ist definitiv eine Option und relativ einfach zu verstehen.

1voto

sateesh Punkte 26553

Über Ihren Ansatz, eine Ausnahme von einem Generator an die verbrauchende Funktion weiterzugeben, können Sie versuchen, einen Fehlercode (eine Reihe von Fehlercodes) zu verwenden, um den Fehler anzuzeigen. Obwohl nicht elegant, ist das ein Ansatz, den Sie in Betracht ziehen können.

Zum Beispiel würde in dem unten stehenden Code das Liefern eines Werts wie -1, wenn Sie einen Satz positiver Ganzzahlen erwartet haben, der aufrufenden Funktion signalisieren, dass ein Fehler aufgetreten ist.

In [1]: def f():
  ...:     yield 1
  ...:     try:
  ...:         2/0
  ...:     except ZeroDivisionError,e:
  ...:         yield -1
  ...:     yield 3
  ...:     

In [2]: g = f()

In [3]: next(g)
Out[3]: 1

In [4]: next(g)
Out[4]: -1

In [5]: next(g)
Out[5]: 3

1voto

Alfe Punkte 51658

Eigentlich sind Generatoren in mehreren Aspekten ziemlich eingeschränkt. Du hast einen gefunden: Das Auslösen von Ausnahmen ist nicht Teil ihrer API.

Du könntest dir das Stackless Python-Zeug wie Greenlets oder Coroutinen ansehen, die viel mehr Flexibilität bieten; aber sich damit zu befassen, wäre hier ein wenig außerhalb des Rahmens.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X