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


8

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

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、例外はキャッチされ、呼び出し元に伝播されます。これは望ましいことなので、本当に処理することを期待している例外のみをキャッチします。

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


handle_*ファンクションではどのようなことをしていますか?
Winston Ewert 2014年

回答:


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

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

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

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

これらのhandle_関数であなたが何をしているのかについて、私が言えることはほとんどありません。


これはサンプルコードです。handle_機能は同じように簡単にエラーを記録し、値をフォールバックを返すか、新しい例外を発生させる、または他のものの任意の数を行う(例えば)コードの短い抜粋である可能性があります。一方、foo()bar()、などの機能が可能性コードの短い断片です。たとえば、がありspam[eggs]、キャッチする必要がありKeyErrorます。
ケビン

1
@Kevin、そうですが、それは私の答えを変えません。ハンドラが何かをreturn編集したりした場合、あなたraiseが言及した問題は発生しません。関数の残りの部分は自動的にスキップされます。実際、問題を解決する簡単な方法は、すべての例外ブロックに戻ることです。あなたがあきらめていることを示すために通常の保釈金やレイズをしていないので、コードは扱いにくいです。
Winston Ewert 2014年

全体的に良い答えですが、私はあなたのポイント1に同意するfoo必要があります- 独自のクリーンアップを実行する必要がある証拠は何ですか? foo何かがうまくいかなかったことを知らせるものであり、正しいクリーンアップ手順が何であるかについての知識がありません。
イーサンファーマン2014年

@EthanFurman、私はクリーンアップの見出しの下でさまざまなアイテムを考えているかもしれません。fooがファイルを開いたり、データベース接続を行ったり、メモリを割り当てたりしている場合は、戻る前にそれらが確実に閉じられたり割り当て解除されたりするのはfooの責任です。それが私がクリーンアップで意味したことです。
Winston Ewert 2014年

ああ、その場合私はあなたに完全に同意します。:)
Ethan Furman 2014年

3

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

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

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

finally:
    cleanup()

2
私は、try:ブロックに絶対に必要なコードよりも多くのコードを配置しないようにするように教えられてきました。この場合、私は心配になりbar()、A上げるFooError誤っこのコードで処理されます。
ケビン

2
すべてのメソッドが非常に具体的な例外を発生させる場合、これは合理的かもしれませんが、それは一般的に真実ではありません。私はこのアプローチに対して強くお勧めします。
Winston Ewert 2014年

@Kevin Try Catch Elseブロックでコードの各行をラップすると、すぐに判読できなくなります。これは、Pythonのインデントルールによって、より適切に行われます。コードが10行の長さの場合、これがどこまでインデントされるかを考えてください。読みやすいか、保守できるか。コードが単一の責任に従う限り、私は上記の例を支持します。そうでない場合は、コードを書き直す必要があります。
BillThor 2014年

@WinstonEwert実際の例では、エラー(例外)の重複が予想されます。ただし、取り扱いも同じだと思います。BazErrorはキーボードの割り込みである可能性があります。4行すべてのコードで適切に処理されると思います。
BillThor 2014年

1

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

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

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

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


IMHOこれは、1)回答を試み、2)シーケンスを非常に明確にするため、受け入れられたものよりも優れたソリューションです。
ボブ

0

まず、を適切に使用すると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()
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.