web-dev-qa-db-ja.com

finallyブロックに入ったら、例外が発生したかどうかを判断する方法は?

finally節に入ったら、例外があったかどうかを知ることはできますか?何かのようなもの:

try:
    funky code
finally:
    if ???:
        print('the funky code raised')

私はこのようなものをもっと乾燥させたいと思っています:

try:
    funky code
except HandleThis:
    # handle it
    raised = True
except DontHandleThis:
    raised = True
    raise
else:
    raised = False
finally:
    logger.info('funky code raised %s', raised)

フラグを設定するためだけに、処理するつもりのない例外をキャッチする必要があるのは好きではありません。


一部の comments がMCVEで「M」を少なくするように要求しているため、ここではユースケースの背景について説明します。実際の問題は、ログレベルのエスカレーションです。

  • ファンキーなコードはサードパーティであり、変更できません。
  • 失敗の例外とスタックトレースには有用な診断情報が含まれていないため、logger.exception例外ブロックの場合、ここでは役に立ちません。
  • ファンキーなコードが発生した場合、表示する必要があるいくつかの情報はレベルDEBUGですでにログに記録されています。エラーを処理することはできませんが、必要な情報がそこにあるため、DEBUGロギングをエスカレートする必要があります。
  • ファンキーなコードはほとんど発生しません。一般的なケースではログレベルをエスカレートしたくありません。これは冗長すぎるためです。

したがって、コードはログキャプチャコンテキスト(ログレコードをインターセプトするカスタムハンドラーをセットアップする)の下で実行され、一部のデバッグ情報は遡及的に再記録されます。

try:
    with LogCapture() as log:
        funky_code()  # <-- third party badness
finally:
    mylog = mylogger.WARNING if <there was exception> else mylogger.DEBUG
    for record in log.captured:
        mylog(record.msg, record.args)
40
wim
raised = True
try:
    funky code
    raised = False
except HandleThis:
    # handle it
finally:
    logger.info('funky code raised %s', raised)

ログレベルの選択に関する質問に追加の背景情報が追加されていることを考えると、これは意図したユースケースに非常に簡単に適合しているように見えます。

mylog = WARNING
try:
    funky code
    mylog = DEBUG
except HandleThis:
    # handle it
finally:
    mylog(...)
31

コンテキストマネージャーを使用する

たとえば、カスタムコンテキストマネージャーを使用できます。

class DidWeRaise:
    __slots__ = ('exception_happened', )  # instances will take less memory

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        # If no exception happened the `exc_type` is None
        self.exception_happened = exc_type is not None

そして、それをtry内で使用します:

try:
    with DidWeRaise() as error_state:
        # funky code
finally:
    if error_state.exception_happened:
        print('the funky code raised')

これはまだ追加の変数ですが、複数の場所で使用する場合はおそらく再利用がはるかに簡単です。そして、自分で切り替える必要はありません。

変数を使用する

Contextmanagerが必要ない場合は、トリガーのロジックを逆にして、no例外が発生した場合にのみonlyを切り替えます。そうすれば、処理したくない例外に対してexceptケースは必要ありません。最も適切な場所は、elseが例外をスローしなかった場合に入力されるtry句です。

exception_happened = True
try:
    # funky code
except HandleThis:
    # handle this kind of exception
else:
    exception_happened = False
finally:
    if exception_happened:
        print('the funky code raised')

そして、「トグル」変数を持つ代わりに既に指摘したように、(この場合)目的のロギング関数に置き換えることができます:

mylog = mylogger.WARNING
try:
    with LogCapture() as log:
        funky_code()
except HandleThis:
    # handle this kind of exception
else:
    # In case absolutely no exception was thrown in the try we can log on debug level
    mylog = mylogger.DEBUG
finally:
    for record in log.captured:
        mylog(record.msg, record.args)

もちろん、あなたのtryの最後にそれを置いても機能します(ここで他の回答が示唆しているように)が、より意味があるのでelse節を好みます(「そのコードはtryブロックに例外がなかった場合にのみ実行されます)」と長期的に維持する方が簡単かもしれません。ただし、変数はさまざまな場所で設定および切り替えられるため、コンテキストマネージャーよりも維持する必要があります。

sys.exc_infoの使用(未処理の例外に対してのみ機能します)

私が言及したい最後のアプローチはおそらくあなたには役に立たないでしょうが、おそらく未処理例外(notキャッチされた例外) exceptブロック内、またはexceptブロック内で発生した)。その場合、 sys.exc_info を使用できます。

import sys

try:
    # funky code
except HandleThis:
    pass
finally:
    if sys.exc_info()[0] is not None:
        # only entered if there's an *unhandled* exception, e.g. NOT a HandleThis exception
        print('funky code raised')
40
MSeifert

さて、実際に既存のコンテキストマネージャーを変更するか、同様のアプローチを使用するだけのように聞こえますが、logbookには実際に FingersCrossedHandler と呼ばれるものがありますまさにあなたが望むことをするでしょう。ただし、次のように自分で実行できます。

@contextmanager
def LogCapture():
    # your existing buffer code here
    level = logging.WARN
    try:
        yield
    except UselessException:
        level = logging.DEBUG
        raise  # Or don't, if you just want it to go away
    finally:
        # emit logs here

元の応答

あなたはこれについて少し横向きに考えています。

あなたはdo例外を処理するつもりです-フラグを設定することで例外を処理しています。たぶんあなたは他に何も気にしない(悪い考えのように思えます)が、an例外が発生したときに何かをすることを気にするなら、あなたは明示的にしたいですそれ。

変数を設定しているが、例外を続行したいという事実は、reallyしたいことは、発生した例外から独自の特定の例外を発生させることであることを意味します:

class MyPkgException(Exception):  pass
class MyError(PyPkgException): pass  # If there's another exception type, you can also inherit from that

def do_the_badness():
    try:
        raise FileNotFoundError('Or some other code that raises an error')
    except FileNotFoundError as e:
        raise MyError('File was not found, doh!') from e
    finally:
        do_some_cleanup()

try:
    do_the_badness()
except MyError as e:
    print('The error? Yeah, it happened')

これは解決します:

  • 処理しようとしている例外を明示的に処理する
  • スタックトレースと元の例外を利用可能にする
  • スローされた例外を処理するために、元の例外を別の場所で処理するコードを許可する
  • いくつかのトップレベルの例外処理コードがMyPkgExceptionをキャッチしてすべての例外をキャッチできるようにして、soいスタックトレースの代わりにNiceステータスで何かを記録して終了できるようにします。
3
Wayne Werner

キャッチした例外を変数に簡単に割り当てて、finallyブロックで使用できます。例:

>>> x = 1
>>> error = None
>>> try:
...     x.foo()
... except Exception as e:
...     error = e
... finally:
...     if error is not None:
...             print(error)
...
'int' object has no attribute 'foo'
2
Jonathan R