例外に情報を追加しますか?


142

私はこのようなことを達成したいです:

def foo():
   try:
       raise IOError('Stuff ')
   except:
       raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
       e.message = e.message + 'happens at %s' % arg1
       raise

bar('arg1')
Traceback...
  IOError('Stuff Happens at arg1')

しかし、私が得るものは:

Traceback..
  IOError('Stuff')

これを達成する方法についての手がかりはありますか?Python 2と3の両方でそれを行う方法?


Exception message属性のドキュメントを探しているときに、このSOの質問を見つけました。BaseException.messageはPython 2.6非推奨になりました。これは、その使用が現在推奨されていないことを示しているようです(ドキュメントに含まれていない理由です)。
martineau '19年

残念ながら、そのリンクはもう機能していないようです。
Michael Scott Cuthbert、

1
@MichaelScottCuthbertはここで良い選択肢だ:itmaybeahack.com/book/python-2.6/html/p02/...
ニールスKeurentjes

ここに、メッセージ属性のステータスと、args属性およびPEP 352との関係についての本当に良い説明があります。これは、スティーブンF.ロットの無料の書籍 『Building Skills in Python』からです。
martineau 2014年

回答:


118

私はこのようにしたいので、タイプをfoo()変更しても、で変更する必要はありませんbar()

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
        foo()
    except Exception as e:
        raise type(e)(e.message + ' happens at %s' % arg1)

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 13, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    raise type(e)(e.message + ' happens at %s' % arg1)
IOError: Stuff happens at arg1

アップデート1

元のトレースバックを保持するわずかな変更を次に示します。

...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e), type(e)(e.message +
                               ' happens at %s' % arg1), sys.exc_info()[2]

bar('arg1')

Traceback (most recent call last):
  File "test.py", line 16, in <module>
    bar('arg1')
  File "test.py", line 11, in bar
    foo()
  File "test.py", line 5, in foo
    raise IOError('Stuff')
IOError: Stuff happens at arg1

アップデート2

Python 3.xの場合、最初の更新のコードは構文的に正しくありません。また、2012-05-16のPEP 352への変更で、message属性をオンBaseExceptionにするという考えが撤回されました(私の最初の更新は2012-03-12に投稿されました)。 。したがって、現在のところ、Python 3.5.2ではとにかく、トレースバックを保持し、関数の例外のタイプをハードコードしないように、これらの行に沿って何かを行う必要がありますbar()。また、次の行があることに注意してください。

During handling of the above exception, another exception occurred:

表示されるトレースバックメッセージ内。

# for Python 3.x
...
def bar(arg1):
    try:
        foo()
    except Exception as e:
        import sys
        raise type(e)(str(e) +
                      ' happens at %s' % arg1).with_traceback(sys.exc_info()[2])

bar('arg1')

アップデート3

答えが原因構文の違いに「いいえ」のように見えるかもしれませんが、Pythonの2と3の両方で動作するような方法があった場合コメンターが尋ね、そこにあるようにヘルパー関数を使用して、その周りのやり方reraise()sixアドインはモジュール上。そのため、何らかの理由でライブラリを使用したくない場合は、以下に簡略化されたスタンドアロンバージョンを示します。

また、例外はreraise()関数内で再度発生するため、発生したトレースバックに表示されますが、最終的な結果は希望どおりです。

import sys

if sys.version_info.major < 3:  # Python 2?
    # Using exec avoids a SyntaxError in Python 3.
    exec("""def reraise(exc_type, exc_value, exc_traceback=None):
                raise exc_type, exc_value, exc_traceback""")
else:
    def reraise(exc_type, exc_value, exc_traceback=None):
        if exc_value is None:
            exc_value = exc_type()
        if exc_value.__traceback__ is not exc_traceback:
            raise exc_value.with_traceback(exc_traceback)
        raise exc_value

def foo():
    try:
        raise IOError('Stuff')
    except:
        raise

def bar(arg1):
    try:
       foo()
    except Exception as e:
        reraise(type(e), type(e)(str(e) +
                                 ' happens at %s' % arg1), sys.exc_info()[2])

bar('arg1')

3
これはバックトレースを失い、既存の例外に情報を追加するという点を打ち負かします。また、> 1引数を取るctorでは例外は機能しません(型は、例外をキャッチする場所から制御できないものです)。
ヴァーツラフSlavík

1
@Václav:追加したアップデートに示されているように、バックトレースが失われるのを防ぐのはかなり簡単です。これはまだ考えられるすべての例外を処理するわけではありませんが、OPの質問で示されたものと同様のケースで機能します。
martineau

1
これではありませんかなり右。type(e)がをオーバーライドすると__str__、望ましくない結果が生じる可能性があります。また、2番目の引数は最初の引数で指定されたコンストラクタに渡されるため、多少意味のないものになりtype(e)(type(e)(e.message)ます。第3に、e.args [0]が導入れたため、e.messageは非推奨になりました。
bukzor 2013

1
それで、Python 2と3の両方で機能するポータブルな方法はありませんか?
エリアスドルネレス2014

1
@martineau exceptブロック内にインポートする目的は何ですか?これは、必要なときにのみインポートすることでメモリを節約するためのものですか?
AllTradesJack 2014

114

あなたがPython 3 解決策を探してここに来た場合、マニュアルに は次のように書かれています:

raise現在処理されている例外を再発生させるためにベアを使用するのではなく)新しい例外を発生させる場合、暗黙の例外コンテキストは、from with raiseを使用することにより、明示的な原因を補足できます。

raise new_exc from original_exc

例:

try:
    return [permission() for permission in self.permission_classes]
except TypeError as e:
    raise TypeError("Make sure your view's 'permission_classes' are iterable. "
                    "If you use '()' to generate a set with a single element "
                    "make sure that there is a comma behind the one (element,).") from e

最終的には次のようになります。

2017-09-06 16:50:14,797 [ERROR] django.request: Internal Server Error: /v1/sendEmail/
Traceback (most recent call last):
File "venv/lib/python3.4/site-packages/rest_framework/views.py", line 275, in get_permissions
    return [permission() for permission in self.permission_classes]
TypeError: 'type' object is not iterable 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
    # Traceback removed...
TypeError: Make sure your view's Permission_classes are iterable. If 
     you use parens () to generate a set with a single element make 
     sure that there is a (comma,) behind the one element.

完全に説明のないTypeErrorメッセージを、元の例外を台無しにせずに解決策へのヒントを持つ素敵なメッセージに変える。


14
結果の例外が元の原因を指し示すため、これが最良の解決策です。詳細を提供してください。
JT、

メッセージを追加できるが、それでも新しい例外を発生させない解決策はありますか?例外インスタンスのメッセージを拡張するだけです。
edcSam

はぁ~~ちゃんと動いてるんですけど、やらなきゃいけない感じがします。メッセージはに格納されますがe.args、これはタプルであるため、変更できません。だから、最初のコピーargs:リストには、その後、その後、タプルとして戻ってそれをコピーし、それを修正args = list(e.args) args[0] = 'bar' e.args = tuple(args)
クリス・

27

foo()を変更したくない、または変更できない場合は、次のようにすることができます。

try:
    raise IOError('stuff')
except Exception as e:
    if len(e.args) >= 1:
        e.args = (e.args[0] + ' happens',) + e.args[1:]
    raise

これは確かに、「上記の例外の処理中に別の例外が発生しました」というメッセージを醜く混乱させることなく、Python 3の問題を解決する唯一のソリューションです。

再レイズラインをスタックトレースに追加する必要がある場合は、raise e代わりに書き込むraiseとうまくいきます。


しかし、この場合、例外がfooで変更されると、バーも変更する必要があります。
anijhaw '19年

1
Exception(上記で編集)をキャッチすると、標準ライブラリの例外(およびExceptionから継承してException .__ init__を呼び出すもの)をキャッチできます。
スティーブハワード

6
より完全な/協力的であることが、元のタプルの他の部分を含む:e.args = ('mynewstr' + e.args[0],) + e.args[1:]
Dubslow

1
@ nmz787これは実際、Python 3に最適なソリューションです。あなたのエラーは正確には何ですか?
クリスチャン

1
@Dubslowとmartineau私はあなたの提案を編集に組み込んだ。
クリスチャン

9

これまでのところ、与えられたすべての答えが好きではありません。彼らはまだ冗長な私見です。コードおよびメッセージ出力のいずれか。

私が欲しいのは、ソース例外を指すスタックトレースで、その間に例外はありません。そのため、新しい例外を作成せず、関連するすべてのスタックフレームの状態を元に元の状態に戻し、そこに導きました。

スティーブ・ハワードは私が拡張したいいい答えを与えました、いいえ、減らしてください... python 3のみに。

except Exception as e:
    e.args = ("Some failure state", *e.args)
    raise

唯一の新しいことは、パラメーターの展開/解凍ですにより、私が使用するのに十分なだけ小さくて簡単になります。

それを試してみてください:

foo = None

try:
    try:
        state = "bar"
        foo.append(state)

    except Exception as e:
        e.args = ("Appending '"+state+"' failed", *e.args)
        raise

    print(foo[0]) # would raise too

except Exception as e:
    e.args = ("print(foo) failed: " + str(foo), *e.args)
    raise

これはあなたに与えるでしょう:

Traceback (most recent call last):
  File "test.py", line 6, in <module>
    foo.append(state)
AttributeError: ('print(foo) failed: None', "Appending 'bar' failed", "'NoneType' object has no attribute 'append'")

シンプルなプリティプリントは、

print("\n".join( "-"*i+" "+j for i,j in enumerate(e.args)))

5

クラス属性にはクラスオブジェクトとクラスインスタンスの両方からアクセスできるため、私が使用した1つの便利なアプローチは、詳細のストレージとしてクラス属性を使用することです。

class CustomError(Exception):
    def __init__(self, details: Dict):
        self.details = details

次に、コードで:

raise CustomError({'data': 5})

そしてエラーをキャッチするとき:

except CustomError as e:
    # Do whatever you want with the exception instance
    print(e.details)

元の例外がスローされてキャッチされなかった場合に、OPがスタックトレースの一部として詳細を出力するように要求しているため、あまり役に立ちません。
カウベルト2017

ソリューションは良いと思います。しかし、その説明は正しくありません。クラス属性は、インスタンス化するときにインスタンスにコピーされます。したがって、インスタンスの属性「details」を変更しても、クラス属性はNoneのままです。とにかく、ここでこの動作が必要です。
Adam Wallner

2

以前の回答とは異なり、これは本当に悪いという例外に直面しても機能し__str__ます。それはない、役に立たないうち要因にするために、しかし、タイプを変更します__str__実装。

タイプを変更しない追加の改善点を見つけたいと思います。

from contextlib import contextmanager
@contextmanager
def helpful_info():
    try:
        yield
    except Exception as e:
        class CloneException(Exception): pass
        CloneException.__name__ = type(e).__name__
        CloneException.__module___ = type(e).__module__
        helpful_message = '%s\n\nhelpful info!' % e
        import sys
        raise CloneException, helpful_message, sys.exc_traceback


class BadException(Exception):
    def __str__(self):
        return 'wat.'

with helpful_info():
    raise BadException('fooooo')

元のトレースバックとタイプ(名前)は保持されます。

Traceback (most recent call last):
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
  File "/usr/lib64/python2.6/contextlib.py", line 34, in __exit__
    self.gen.throw(type, value, traceback)
  File "re_raise.py", line 5, in helpful_info
    yield
  File "re_raise.py", line 20, in <module>
    raise BadException('fooooo')
__main__.BadException: wat.

helpful info!

2

例外に情報を追加したい場合によく使用するコードのスニペットを提供します。私はPython 2.7と3.6の両方で動作します。

import sys
import traceback

try:
    a = 1
    b = 1j

    # The line below raises an exception because
    # we cannot compare int to complex.
    m = max(a, b)  

except Exception as ex:
    # I create my  informational message for debugging:
    msg = "a=%r, b=%r" % (a, b)

    # Gather the information from the original exception:
    exc_type, exc_value, exc_traceback = sys.exc_info()

    # Format the original exception for a nice printout:
    traceback_string = ''.join(traceback.format_exception(
        exc_type, exc_value, exc_traceback))

    # Re-raise a new exception of the same class as the original one, 
    # using my custom message and the original traceback:
    raise type(ex)("%s\n\nORIGINAL TRACEBACK:\n\n%s\n" % (msg, traceback_string))

上記のコードの結果は次のようになります。

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-6-09b74752c60d> in <module>()
     14     raise type(ex)(
     15         "%s\n\nORIGINAL TRACEBACK:\n\n%s\n" %
---> 16         (msg, traceback_string))

TypeError: a=1, b=1j

ORIGINAL TRACEBACK:

Traceback (most recent call last):
  File "<ipython-input-6-09b74752c60d>", line 7, in <module>
    m = max(a, b)  # Cannot compare int to complex
TypeError: no ordering relation is defined for complex numbers


これは質問で提供された例から少し逸脱していることはわかっていますが、それでも、誰かがそれが役立つと思うことを望みます。


1

別のものから継承する独自の例外を定義し、独自のコンストラクタを作成して値を設定できます。

例えば:

class MyError(Exception):
   def __init__(self, value):
     self.value = value
     Exception.__init__(self)

   def __str__(self):
     return repr(self.value)

2
message元の例外のに何かを変更/追加する必要はありません(ただし、修正できると思います)。
martineau '19年

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