web-dev-qa-db-ja.com

ネストされたtry / except / elseをクリーンアップする方法は?

コードを書くとき、私はしばしば次のようなことをしたいです:

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

明らかに、これは完全に判読できません。しかし、それは比較的単純なアイデアを表しています。それぞれの例外ハンドラーを使用して一連の関数(または短いコードスニペット)を実行し、関数が失敗するとすぐに停止します。 Pythonがこのコードに構文糖を提供する可能性があると思います。おそらくこのようなものです:

_# 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()
_

例外が発生しない場合、これはfoo();bar();baz();qux();cleanup()と同等です。例外が発生した場合、それらは適切な例外ハンドラー(存在する場合)によって処理され、cleanup()にスキップします。特に、bar()FooErrorまたはBazErrorを発生させる場合、例外はnotがキャッチされ、呼び出し元に伝達されます。これは望ましいことなので、本当に処理することを期待している例外のみをキャッチします。

構文の醜さに関係なく、この種のコードは一般的に単に悪い考えですか?もしそうなら、それをどのようにリファクタリングしますか?コンテキストマネージャーを使用して複雑さの一部を吸収できると思いますが、それが一般的なケースでどのように機能するかは本当にわかりません。

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

handle_fooは何をしますか?例外処理ブロックで通常行うことはいくつかあります。

  1. エラー後のクリーンアップ:ただし、この場合、foo()はクリーンアップする必要があります。さらに、ほとんどのクリーンアップジョブはwithで最適に処理されます
  2. ハッピーパスに回復します。ただし、残りの機能を続行しないため、これを実行していません。
  3. 例外タイプを変換します。ただし、別の例外をスローしていません
  4. エラーをログに記録します。ただし、タイプごとに特別な例外ブロックを設定する必要はありません。

例外処理でおかしなことをしているようです。ここでの質問は、例外を異常な方法で使用することに関する単純な症状です。あなたは典型的なパターンに陥っていません、そしてそれがこれが厄介になった理由です。

これらのhandle_関数で何をしているのかをよく理解していなければ、私が言えることはそれだけです。

8
Winston Ewert

戻る前に処理する必要のある例外をスローする一連のコマンドがあるようです。コードと例外処理を別々の場所にグループ化してみてください。私はこれがあなたの意図することをすると信じています。

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

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

finally:
    cleanup()
3
BillThor

必要に応じて、いくつかの方法があります。

これがループの方法です:

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()

以下は、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()

個人的にはループ版の方が読みやすいと思います。

1
Ethan Furman

まず、withを適切に使用すると、多くの例外処理コードを削減または排除できるため、保守性と可読性の両方が向上します。

これで、さまざまな方法でネストを減らすことができます。他のポスターはすでにいくつか提供していますので、ここに私自身のバリエーションがあります:

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