Pythonでtry-except-elseを使用することは良い習慣ですか?


437

Pythonでは時々、次のようなブロックが表示されます。

try:
   try_this(whatever)
except SomeException as exception:
   #Handle exception
else:
   return something

try-except-elseが存在する理由は何ですか?

フロー制御を実行するために例外を使用しているので、そのようなプログラミングは好きではありません。でも、もしそれが言語に含まれているなら、それには正当な理由があるに違いないですね。

例外はエラーはなく、例外的な状況でのみ使用する必要があることを理解しています(例:ファイルをディスクに書き込もうとして、空き領域がない、または権限がない可能性があります)。フローではないコントロール。

通常、私は例外を次のように処理します:

something = some_default_value
try:
    something = try_this(whatever)
except SomeException as exception:
    #Handle exception
finally:
    return something

または、例外が発生しても何も返さない場合は、次のようにします。

try:
    something = try_this(whatever)
    return something
except SomeException as exception:
    #Handle exception

回答:


665

「それが無知なのかどうかはわかりませんが、例外を使用してフロー制御を実行しているため、そのようなプログラミングは好きではありません。」

Pythonの世界では、フロー制御に例外を使用することは一般的であり、正常です。

Pythonコアの開発者でさえフロー制御に例外を使用し、そのスタイルは言語に深く組み込まれています(つまり、イテレータープロトコルはStopIterationを使用してループの終了を通知します)。

さらに、try-except-styleは、「look-before-you-leap」構成の一部に固有の競合状態を防ぐために使用されます。たとえば、os.path.existsをテストすると、使用するまでに情報が古くなる可能性があります。同様に、Queue.fullは古い情報を返します。try-を除き、他のスタイルは、これらのケースでは、より信頼性の高いコードを生成します。

「例外はエラーではなく、例外的な状況でのみ使用する必要があることを理解しています」

他のいくつかの言語では、その規則は彼らの図書館に反映されているように彼らの文化的規範を反映しています。「ルール」は、これらの言語のパフォーマンスに関する考慮事項にも一部基づいています。

Pythonの文化的規範は多少異なります。多くの場合、制御フローには例外を使用する必要あります。また、Pythonで例外を使用しても、一部のコンパイル済み言語の場合のように、周囲のコードや呼び出しコードが遅くなることはありません(つまり、CPythonは、実際に例外を使用するかどうかに関係なく、すべてのステップで例外チェック用のコードをすでに実装しています)。

言い換えれば、「例外は例外的である」というあなたの理解は、他のいくつかの言語では理にかなうルールですが、Pythonではそうではありません。

「しかし、それが言語自体に含まれているのであれば、それには正当な理由があるに違いないね」

例外は、競合状態を回避するのに役立つだけでなく、ループの外でエラー処理をプルする場合にも非常に役立ちます。これは、自動ループ不変コードモーションを持たない傾向があるインタプリタ言語で必要な最適化です。

また、問題を処理する機能が問題が発生した場所から遠く離れている一般的な状況では、例外によってコードがかなり簡略化される場合があります。たとえば、ビジネスロジック用の最上位のユーザーインターフェイスコード呼び出しコードを使用して、低レベルのルーチンを呼び出すのが一般的です。低レベルのルーチンで発生する状況(データベースアクセスの一意のキーの重複レコードなど)は、トップレベルのコード(既存のキーと競合しない新しいキーをユーザーに要求するなど)でのみ処理できます。この種の制御フローに例外を使用すると、中間レベルのルーチンが問題を完全に無視して、フロー制御のその側面からうまく切り離すことができます。

あり、ここでの例外のindispensibilityの素敵なブログ記事を

また、このスタックオーバーフローの回答を参照してください:例外は本当に例外的なエラーですか?

「try-except-elseが存在する理由は何ですか?」

else節自体は興味深いものです。例外はないが、finally-clauseの前に実行されます。それが主な目的です。

else-clauseがない場合、ファイナライズの前に追加のコードを実行する唯一のオプションは、try-clauseにコードを追加する不器用な方法です。これは、tryブロックで保護することを意図していないコードで例外を発生させるリスクがあるため、不器用です。

ファイナライズの前に追加の保護されていないコードを実行するユースケースはあまり発生しません。したがって、公開されたコードに多くの例が表示されるとは期待しないでください。やや珍しいです。

else-clauseのもう1つの使用例は、例外が発生しない場合に発生する必要があり、例外が処理されるときに発生しないアクションを実行することです。例えば:

recip = float('Inf')
try:
    recip = 1 / f(x)
except ZeroDivisionError:
    logging.info('Infinite result')
else:
    logging.info('Finite result')

別の例は、ユニットテストランナーで発生します。

try:
    tests_run += 1
    run_testcase(case)
except Exception:
    tests_failed += 1
    logging.exception('Failing test case: %r', case)
    print('F', end='')
else:
    logging.info('Successful test case: %r', case)
    print('.', end='')

最後に、tryブロックでのelse節の最も一般的な使用法は、少しの美化です(例外的な結果と非例外的な結果を同じレベルのインデントで揃えます)。この使用は常にオプションであり、必ずしも必要ではありません。


28
「これは、try-blockで保護することを意図していないコードで例外を発生させるリスクがあるため、不格好です。」これはここで最も重要な学習です
Felix Dombek '15年

2
答えてくれてありがとう。try-except-elseの使用例を探している読者は、shutilのcopyfile
suripoori

2
ポイントは、try句が成功した場合にのみelse句が実行されることです。
ジョナサン

172

try-except-elseが存在する理由は何ですか?

tryブロックは、あなたが予想されるエラーを処理することができます。exceptブロックはあなたが処理するために用意されている例外をキャッチする必要があります。予期しないエラーを処理すると、コードが誤った動作を行い、バグを隠す可能性があります。

elseエラーがなかった場合句が実行され、そしてその中でコードを実行していないことにより、tryブロック、予期しないエラーをキャッチ避けます。繰り返しになりますが、予期しないエラーをキャッチするとバグを隠すことができます。

例えば:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    return something

「try、except」スイートには、2つのオプションの節がelseありfinallyます。ですから、それは実際try-except-else-finallyです。

elsetryブロックからの例外がない場合にのみ評価されます。これにより、以下のより複雑なコードを簡略化できます。

no_error = None
try:
    try_this(whatever)
    no_error = True
except SomeException as the_exception:
    handle(the_exception)
if no_error:
    return something

したがって、else(バグを作成する可能性がある)代替案と比較すると、コード行が減り、コードベースが読みやすく、保守可能で、バグが少ないことがわかります。

finally

finally returnステートメントで別の行が評価されている場合でも、何が実行されます。

疑似コードで分解

コメントを付けて、すべての機能を示す可能な限り小さい形式で、これを分解することが役立つ場合があります。これが構文的に正しい(ただし、名前が定義されていない限り実行できない)疑似コードが関数内にあると想定します。

例えば:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle_SomeException(the_exception)
    # Handle a instance of SomeException or a subclass of it.
except Exception as the_exception:
    generic_handle(the_exception)
    # Handle any other exception that inherits from Exception
    # - doesn't include GeneratorExit, KeyboardInterrupt, SystemExit
    # Avoid bare `except:`
else: # there was no exception whatsoever
    return something()
    # if no exception, the "something()" gets evaluated,
    # but the return will not be executed due to the return in the
    # finally block below.
finally:
    # this block will execute no matter what, even if no exception,
    # after "something" is eval'd but before that value is returned
    # but even if there is an exception.
    # a return here will hijack the return functionality. e.g.:
    return True # hijacks the return in the else clause above

代わりに、ブロック内のコードをブロック内のブロックに含めることができます。例外がなければ実行されますが、そのコード自体がキャッチしている種類の例外を発生させたらどうなるでしょうか。それをブロックに残すことは、そのバグを隠すでしょう。elsetrytry

tryブロック内のコード行を最小限に抑えて、コードが失敗した場合に大音量で失敗させたいという原則のもと、予期しない例外をキャッチしないようにしています。これがベストプラクティスです。

例外はエラーではないと私は理解しています

Pythonでは、ほとんどの例外はエラーです。

pydocを使用して、例外の階層を表示できます。たとえば、Python 2の場合:

$ python -m pydoc exceptions

またはPython 3:

$ python -m pydoc builtins

階層を提供します。ほとんどの種類のExceptionエラーはエラーであることがわかりますが、Pythonはforループの終了(StopIteration)などにエラーの一部を使用します。これはPython 3の階層です:

BaseException
    Exception
        ArithmeticError
            FloatingPointError
            OverflowError
            ZeroDivisionError
        AssertionError
        AttributeError
        BufferError
        EOFError
        ImportError
            ModuleNotFoundError
        LookupError
            IndexError
            KeyError
        MemoryError
        NameError
            UnboundLocalError
        OSError
            BlockingIOError
            ChildProcessError
            ConnectionError
                BrokenPipeError
                ConnectionAbortedError
                ConnectionRefusedError
                ConnectionResetError
            FileExistsError
            FileNotFoundError
            InterruptedError
            IsADirectoryError
            NotADirectoryError
            PermissionError
            ProcessLookupError
            TimeoutError
        ReferenceError
        RuntimeError
            NotImplementedError
            RecursionError
        StopAsyncIteration
        StopIteration
        SyntaxError
            IndentationError
                TabError
        SystemError
        TypeError
        ValueError
            UnicodeError
                UnicodeDecodeError
                UnicodeEncodeError
                UnicodeTranslateError
        Warning
            BytesWarning
            DeprecationWarning
            FutureWarning
            ImportWarning
            PendingDeprecationWarning
            ResourceWarning
            RuntimeWarning
            SyntaxWarning
            UnicodeWarning
            UserWarning
    GeneratorExit
    KeyboardInterrupt
    SystemExit

コメント者は尋ねました:

外部APIにpingするメソッドがあり、APIラッパーの外側のクラスで例外を処理したい場合、except句の下のメソッドからeを返すだけですか?eは例外オブジェクトですか?

いいえ、例外は返されませんraise。スタックトレースを保持するために、ベアでそれを再レイズするだけです。

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise

または、Python 3では、新しい例外を発生させ、例外チェーンを使用してバックトレースを保持できます。

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    raise DifferentException from the_exception

ここ私の答えを詳しく説明します


賛成です!ハンドル部分の中で通常何をしますか?外部APIをpingするメソッドがあり、APIラッパーの外側のクラスで例外を処理したいとします。eが例外オブジェクトであるexcept節の下のメソッドから単にeを返しますか?
PirateApp 2018

1
@PirateAppありがとうございます!いいえ、それを返さない、あなたはおそらくリレイズ裸でなければならないraiseか、例外のチェーンを行う-しかし、そのトピックによりますと、ここでカバーさ:stackoverflow.com/q/2052390/541136を -私はおそらく私のした後、これらのコメントを削除しますあなたはそれらを見てきました。
アーロンホール

詳細をありがとうございます!今すぐ投稿を通過
PirateApp 2018

36

Pythonは、例外は例外的な場合にのみ使用すべきであるという考えに同意していません。実際、イディオムは「許可ではなく、許しを求める」ものです。つまり、フロー制御のルーチンの一部として例外を使用することは完全に許容され、実際には推奨されます。

この方法で作業すると、いくつかの問題を回避できるため(これは明らかな例として、競合状態が回避されることが多いため)、コードが少し読みやすくなる傾向があります。

処理する必要があるユーザー入力を受け取り、デフォルトがすでに処理されている状況を想像してください。try: ... except: ... else: ...構造は非常に読みやすいコードになり:

try:
   raw_value = int(input())
except ValueError:
   value = some_processed_value
else: # no error occured
   value = process_value(raw_value)

他の言語での動作と比較してください。

raw_value = input()
if valid_number(raw_value):
    value = process_value(int(raw_value))
else:
    value = some_processed_value

利点に注意してください。値が有効であることを確認して個別に解析する必要はありません。一度実行されます。また、コードはより論理的な進行に従います。メインのコードパスが最初で、「機能しない場合はこれを実行」が続きます。

この例は当然少し工夫されていますが、この構造の場合があることを示しています。


15

Pythonでtry-except-elseを使用することは良い習慣ですか?

これに対する答えは、それがコンテキスト依存であるということです。これを行う場合:

d = dict()
try:
    item = d['item']
except KeyError:
    item = 'default'

Pythonをよく知らないことを示しています。この機能はdict.getメソッドにカプセル化されています。

item = d.get('item', 'default')

try/ exceptブロックは、はるかに視覚的に雑然として効率的にアトミック方法で、単一の行で実行することができるものの書き込みの方法冗長です。これが当てはまるケースは他にもあります。

ただし、すべての例外処理を回避する必要があるという意味ではありません。場合によっては、競合状態を回避することが望ましいです。ファイルが存在するかどうかを確認せずに、ファイルを開こうとして、適切なIOErrorをキャッチします。単純さと読みやすさのために、これをカプセル化するか、適切なものとして除外してください。

Zen of Pythonを読んで、緊張している原則があることを理解し、その中のいずれかのステートメントに過度に依存しているドグマに注意してください。


12

try-except-else-finallyに関するすべてを示す次の例を参照してください。

for i in range(3):
    try:
        y = 1 / i
    except ZeroDivisionError:
        print(f"\ti = {i}")
        print("\tError report: ZeroDivisionError")
    else:
        print(f"\ti = {i}")
        print(f"\tNo error report and y equals {y}")
    finally:
        print("Try block is run.")

それを実装して、

    i = 0
    Error report: ZeroDivisionError
Try block is run.
    i = 1
    No error report and y equals 1.0
Try block is run.
    i = 2
    No error report and y equals 0.5
Try block is run.

3
これは、非常に簡単な例であり、(非常に急いでいる可能性がある)誰かに長い抽象的な説明を読むことを要求せずに、完全なtry句をすばやく示します。(もちろん、急いでいない場合は、戻ってアブストラクト全体を読む必要があります。)
GlobalSoftwareSociety

6

ただし、tryブロックでelseブロックを使用するのと同じではないため、finallyブロックの使用には注意が必要です。finallyブロックは、tryの結果に関係なく実行されます。

In [10]: dict_ = {"a": 1}

In [11]: try:
   ....:     dict_["b"]
   ....: except KeyError:
   ....:     pass
   ....: finally:
   ....:     print "something"
   ....:     
something

誰もがelseブロックを使用すると、コードが読みやすくなり、例外がスローされない場合にのみ実行されると指摘している

In [14]: try:
             dict_["b"]
         except KeyError:
             pass
         else:
             print "something"
   ....:

最終的には常に実行されることを知っています。そのため、常にデフォルト値を設定することで私たちの利点を活用できるので、例外の場合はそれが返され、例外の場合にそのような値を返さない場合は、最後のブロックを削除するには十分です。ところで、例外キャッチでパスを使用することは、私がこれまでやったことのないことです:)
ファンアントニオゴメスモリアーノ2013

@Juan Antonio Gomez Moriano、私のコーディングブロックは例示のみを目的としています。おそらくパスも使用しないでしょう
グレッグ

4

あなたがこれを見たときはいつでも:

try:
    y = 1 / x
except ZeroDivisionError:
    pass
else:
    return y

またはこれでも:

try:
    return 1 / x
except ZeroDivisionError:
    return None

代わりにこれを考慮してください:

import contextlib
with contextlib.suppress(ZeroDivisionError):
    return 1 / x

1
それは単に私の友人の例であったので、それは私の質問に答えません。
ファンアントニオゴメスモリアーノ2017年

Pythonでは、例外はエラーではありません。彼らも例外ではありません。Pythonでは、フロー制御に例外を使用するのが普通で自然です。これは、標準ライブラリにcontextlib.suppress()が含まれていることで証明されています。ここレイモンドヘッティンガーの回答を参照してください:stackoverflow.com/a/16138864/1197429を(レイモンドは、コアPythonの貢献者、およびすべてのものPython的に権威ある!)
ラジブBakuleshシャー

4

他に誰もこの意見を投稿していないからといって、

彼らはほとんどの人に慣れていないので、else節を避けtry/excepts てください

キーワードとは異なりtryexcept、及びfinally、の意味else句は自明ではありません。読みにくくなります。これはあまり使用されないため、コードを読む人はドキュメントを再確認して、何が起こっているのかを理解していることを確認する必要があります。

(私がtry/except/elseコードベースでを見つけたので正確にこの回答を書いています。それはwtfの瞬間を引き起こし、グーグルすることを余儀なくさせました)。

したがって、OPの例のようなコードが見られるところはどこでも:

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
else:
    # do some more processing in non-exception case
    return something

私はリファクタリングしたいと思います

try:
    try_this(whatever)
except SomeException as the_exception:
    handle(the_exception)
    return  # <1>
# do some more processing in non-exception case  <2>
return something
  • <1>明示的な返品。例外的なケースでは、作業が終了したことを明確に示します

  • <2>小さな副次的効果として、else以前はブロック内にあったコードが1レベル低くなっています。


1
悪魔の主張の反論:より多くの人々がそれを使うほど、それはよりよく採用されます。読みやすさが輸入であることに私は同意しますが、考えるための食べ物です。そうは言っても、誰かがtry-elseを理解したら、多くの場合、他の方法よりもはるかに読みやすいと主張します。
bob

2

これは、Pythonのtry-except-else-finallyブロックを理解する方法に関する私の簡単なスニペットです。

def div(a, b):
    try:
        a/b
    except ZeroDivisionError:
        print("Zero Division Error detected")
    else:
        print("No Zero Division Error")
    finally:
        print("Finally the division of %d/%d is done" % (a, b))

div 1/1を試してみましょう:

div(1, 1)
No Zero Division Error
Finally the division of 1/1 is done

div 1/0を試してみましょう

div(1, 0)
Zero Division Error detected
Finally the division of 1/0 is done

1
これは、なぜelseコードをtry内に配置できないのかを実証できないと思います
Mojimi

-4

OP、あなたは正しいです。 Pythonでのtry / exceptの後のelseは醜いです。何も必要とされない別のフロー制御オブジェクトにつながります。

try:
    x = blah()
except:
    print "failed at blah()"
else:
    print "just succeeded with blah"

完全に同等なものは次のとおりです。

try:
    x = blah()
    print "just succeeded with blah"
except:
    print "failed at blah()"

これは、else句よりもはるかに明確です。else after try / exceptは頻繁に記述されないため、影響が何かを理解するには少し時間がかかります。

できることだからといって、すべきことはありません。

誰かが便利だと思ったので、言語に多くの機能が追加されました。問題は、機能が多いほど、人々が通常これらのベルやホイッスルを使用しないために、明確で明白でないものが少なくなることです。

ちょうど私の5セントここ。私は後ろに来て、彼らがスマートであると思い、それが混乱するだけのときに非常にタイトで超効率的な方法でコードを書きたい大学の開発者のうちの1年目に書かれた多くのコードを片付けなければなりません後で読んだり修正したりすること。私は毎日読みやすくするために投票し、日曜日には2回投票します。


15
あなたが正しい。それは完全に明確で同等です...あなたのprint声明が失敗しない限り。場合はどうなるのx = blah()リターンのA strが、あなたのprint文はありますかprint 'just succeeded with blah. x == %d' % x?これでTypeError、処理する準備ができていない生成が発生しました。あなたはx = blah()例外の原因を見つけるために調査していますが、そこにもありません。私はこれ(または同等のもの)を何度も行ってきましたが、elseこの間違いを犯さないようにしていました。今私はよく知っています。:-D
ダグR.

2
...そしてはい、そうです。このelse句はきれいなステートメントではなく、慣れるまでは直感的ではありません。しかし、それから、finally私が最初にそれを使い始めたときもそうではありませんでした...
Doug R.

2
Doug R.をエコーするにelse句のステートメント中の例外がによってキャッチされないため、同等ではありませんexcept
alastair 2015

if ... except ... elseの方が読みやすい場合、そうでない場合は、「ああ、tryブロックの後で、例外なしで、tryブロックの外のステートメントに移動する」ので、elseを使用すると、構文が意味的に少し接続される傾向があります。いいイモ。また、トラップされていないステートメントを最初のtryブロックの外に残すことをお勧めします。
カウベルト

1
@DougR。「あなたはx = blah()例外の原因を見つけるために検査しています」、tracebackなぜあなたは間違った場所から例外の原因を検査するのですか?
ニーム、2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.