web-dev-qa-db-ja.com

Pythonでtry-except-elseを使うのは良い習慣ですか?

時々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

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

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

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

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

「例外はエラーではなく、例外的な条件にのみ使用されるべきであると理解している」

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

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

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

「しかし、それが言語自体に含まれている場合、それには十分な理由があるに違いありませんよね?」

例外は、競合状態の回避に役立つだけでなく、ループの外側でエラー処理をプルする場合にも非常に役立ちます。これは、自動 loop invariant code motion を使用しない傾向があるインタープリター言語で必要な最適化です。

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

ここに例外の不可欠性に関する素敵なブログ投稿 があります。

また、このStack Overflowの回答を参照してください: 例外は本当に例外的なエラーですか?

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

Else節自体は興味深いものです。例外がなく、最終節の前に実行されます。それが主な目的です。

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

別の例は、unittestランナーで発生します。

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-clauseを使用する最も一般的な方法は、少し美化することです(同じレベルのインデントで例外的な結果と非例外的な結果を調整する)。この使用は常にオプションであり、厳密に必要というわけではありません。

601

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つのオプション句elsefinallyがあります。だから実際にはtry-except-else-finallyです。

elseは、tryブロックからの例外がない場合にのみ評価されます。これにより、以下のより複雑なコードを単純化することができます。

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

たとえ別の行がreturn文で評価されていても、finallyは何に関係なく実行されます。

擬似コードで分割

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

例えば:

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

couldelseブロック内のコードを代わりにtryブロックに含めます。例外がない場合は実行されますが、そのコード自体がそのような例外を発生させた場合はどうなりますか。キャッチ? tryブロックに置いたままにすると、そのバグは隠されます。

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

例外がエラーではないことは私の理解です

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

Pydocを使って例外の階層を見ることができます。例えば、Python 2では:

$ python -m pydoc exceptions

またはPython 3:

$ python -m pydoc builtins

階層を教えてください。 PythonはExceptionループ(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

私の答えは で詳しく説明します。

136
Aaron Hall

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

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

例は当然少し工夫がされています、しかしそれはこの構造のためのケースがあることを示しています。

33
Gareth Latty

Pythonでtry-except-elseを使うのは良い習慣ですか?

これに対する答えはそれが文脈依存であるということです。あなたがこれをするならば:

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

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

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

try/exceptブロックは、アトミックメソッドを使用して1行で効率的に実行できるものを、はるかに視覚的に整理された冗長な方法で記述します。これが本当である他のケースがあります。

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

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

15
Aaron Hall

次の例を見てください。これは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.
7
JawSaw

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

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"
   ....:
5
Greg

あなたがこれを見るたびに:

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
4

これは、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
2
zakiakhmad