it-swarm.com.de

Wie bereinige versuche versuchen / außer / sonst?

Beim Schreiben von Code möchte ich oft Folgendes tun:

try:
    foo()
except FooError:
    handle_foo()
else:
    try:
        bar()
    except BarError:
        handle_bar()
    else:
        try:
            baz()
        except BazError:
            handle_baz()
        else:
            qux()
finally:
    cleanup()

Dies ist offensichtlich völlig unlesbar. Es drückt jedoch eine relativ einfache Idee aus: Führen Sie eine Reihe von Funktionen (oder Kurzcode-Snippets) mit jeweils einem Ausnahmebehandler aus und stoppen Sie, sobald eine Funktion ausfällt. Ich stelle mir vor, Python könnte syntaktischen Zucker für diesen Code liefern, vielleicht so etwas:

# NB: This is *not* valid Python
try:
    foo()
except FooError:
    handle_foo()
    # GOTO finally block
else try:
    bar()
except BarError:
    handle_bar()
    # ditto
else try:
    baz()
except BazError:
    handle_baz()
    # ditto
else:
    qux()
finally:
    cleanup()

Wenn keine Ausnahmen ausgelöst werden, entspricht dies foo();bar();baz();qux();cleanup(). Wenn Ausnahmen ausgelöst werden, werden sie vom entsprechenden Ausnahmebehandler (falls vorhanden) behandelt, und wir springen zu cleanup(). Insbesondere wenn bar() ein FooError oder BazError auslöst, wird die Ausnahme nicht abgefangen und an den Aufrufer weitergegeben. Dies ist wünschenswert, damit wir nur Ausnahmen abfangen, die wir wirklich erwarten.

Ist diese Art von Code unabhängig von der syntaktischen Hässlichkeit im Allgemeinen nur eine schlechte Idee? Wenn ja, wie würden Sie es umgestalten? Ich stelle mir vor, dass Kontextmanager verwendet werden könnten, um einen Teil der Komplexität zu absorbieren, aber ich verstehe nicht wirklich, wie das im allgemeinen Fall funktionieren würde.

8
Kevin
try:
    foo()
except FooError:
    handle_foo()
else:
    ...
finally:
    cleanup()

Was macht handle_foo tun? Es gibt einige Dinge, die wir normalerweise in Ausnahmebehandlungsblöcken tun.

  1. Bereinigung nach dem Fehler: In diesem Fall sollte foo () nach sich selbst bereinigen und uns dies nicht überlassen. Darüber hinaus werden die meisten Bereinigungsjobs am besten mit with erledigt
  2. Stellen Sie den glücklichen Weg wieder her: Aber Sie tun dies nicht, da Sie nicht mit den restlichen Funktionen fortfahren.
  3. Übersetzt den Ausnahmetyp: Sie lösen jedoch keine weitere Ausnahme aus
  4. Protokollieren Sie den Fehler: Es sollten jedoch keine speziellen Ausnahmeblöcke für jeden Typ erforderlich sein.

Es scheint mir, dass Sie bei der Ausnahmebehandlung etwas Seltsames tun. Ihre Frage hier ist einfach ein Symptom für die ungewöhnliche Verwendung von Ausnahmen. Sie fallen nicht in das typische Muster, und deshalb ist dies unangenehm geworden.

Ohne eine bessere Vorstellung davon, was Sie in diesen handle_ Funktionen, die ungefähr alles sind, was ich sagen kann.

8
Winston Ewert

Es scheint, als hätten Sie eine Folge von Befehlen, die möglicherweise eine Ausnahme auslösen, die vor der Rückkehr behandelt werden muss. Versuchen Sie, Ihren Code und die Ausnahmebehandlung an verschiedenen Orten zu gruppieren. Ich glaube, das macht, was Sie beabsichtigen.

try:
    foo()
    bar()
    baz()
    qux()

except FooError:
    handle_foo()
except BarError:
    handle_bar()
except BazError:
    handle_baz()

finally:
    cleanup()
3
BillThor

Es gibt verschiedene Möglichkeiten, je nachdem, was Sie benötigen.

Hier ist ein Weg mit Schleifen:

try:
    for func, error, err_handler in (
            (foo, FooError, handle_foo),
            (bar, BarError, handle_bar),
            (baz, BazError, handle_baz),
        ):
        try:
            func()
        except error:
            err_handler()
            break
finally:
    cleanup()

Hier ist ein Weg mit einem Exit nach dem error_handler:

def some_func():
    try:
        try:
            foo()
        except FooError:
            handle_foo()
            return
        try:
            bar()
        except BarError:
            handle_bar()
            return
        try:
            baz()
        except BazError:
            handle_baz()
            return
        else:
            qux()
    finally:
        cleanup()

Persönlich denke ich, dass die Loop-Version leichter zu lesen ist.

1
Ethan Furman

Erstens kann eine angemessene Verwendung von with häufig einen Großteil des Codes für die Ausnahmebehandlung reduzieren oder sogar eliminieren, wodurch sowohl die Wartbarkeit als auch die Lesbarkeit verbessert werden.

Jetzt können Sie die Verschachtelung auf viele Arten reduzieren. Andere Poster haben bereits einige bereitgestellt. Hier ist meine eigene Variante:

for _ in range(1):
    try:
        foo()
    except FooError:
        handle_foo()
        break
    try:
        bar()
    except BarError:
        handle_bar()
        break
    try:
        baz()
    except BazError:
        handle_baz()
        break
    qux()
cleanup()
0
Rufflewind