私はモジュールを書いていますが、発生する可能性のある例外(たとえば、すべてのFooError
モジュールの特定の例外のfoo
抽象クラスから継承する)の統一された例外階層を持ちたいと思っています。これにより、モジュールのユーザーはこれらの特定の例外をキャッチし、必要に応じて明確に処理できます。ただし、モジュールから発生する例外の多くは、他の例外のために発生します。例えばファイルのOSErrorが原因で、あるタスクで失敗します。
必要なのは、キャッチされた例外を「ラップ」して、タイプとメッセージが異なるようにすることです。例外をキャッチするものは何でも。しかし、既存のタイプ、メッセージ、スタックトレースを失いたくありません。これは、問題をデバッグしようとしている人にとって有益な情報です。最上位の例外ハンドラーは、伝播スタックをさらに上に移動する前に例外を装飾しようとしており、最上位のハンドラーが遅すぎるため、良くありません。
これは、モジュールfoo
の特定の例外タイプを既存のタイプ(たとえば、class FooPermissionError(OSError, FooError)
)から派生させることで部分的に解決されますが、既存の例外インスタンスを新しいものにラップするのは簡単ではありませんメッセージを入力または変更しません。
Pythonの PEP 3134 「Exception Chaining and Embedded Tracebacks」では、Python 3.0で受け入れられた変更について説明し、新しい例外が既存の例外の処理。
私がやろうとしていることは関連しています:以前のPythonバージョンでも動作する必要があり、チェーンのためではなく、ポリモーフィズムのためだけに必要です。これを行う正しい方法は何ですか? ?
Python 3導入例外連鎖( PEP 3134 )。これにより、例外を発生させるときに、既存の例外を「原因」として引用できます。
try:
frobnicate()
except KeyError as exc:
raise ValueError("Bad grape") from exc
これにより、キャッチされた例外は、新しい例外の一部(「原因」)になり、新しい例外をキャッチするすべてのコードで使用できます。
この機能を使用すると、__cause__
属性が設定されています。組み込みの例外ハンドラーも 例外の「原因」と「コンテキスト」を報告する方法を知っています トレースバックとともに。
Python 2では、このユースケースには良い答えがないと思われます( Ian Bicking および Ned Batchelder )。残念。
Sys.exc_info()を使用してトレースバックを取得し、トレースバックを使用して新しい例外を発生させることができます(PEPの言及どおり)。古いタイプとメッセージを保持したい場合は、例外でそれを行うことができますが、それは例外をキャッチするものがそれを探す場合にのみ有用です。
例えば
import sys
def failure():
try: 1/0
except ZeroDivisionError, e:
type, value, traceback = sys.exc_info()
raise ValueError, ("You did something wrong!", type, value), traceback
もちろん、これは実際にはそれほど便利ではありません。そうであれば、そのPEPは必要ありません。私はそれをすることをお勧めしません。
いずれの例外 キャッチした独自の例外タイプを作成できます。
_class NewException(CaughtException):
def __init__(self, caught):
self.caught = caught
try:
...
except CaughtException as e:
...
raise NewException(e)
_
しかし、ほとんどの場合、例外をキャッチして処理し、raise
元の例外(およびトレースバックを保持)またはraise NewException()
のいずれかを使用する方が簡単だと思います。私があなたのコードを呼び出していて、カスタム例外の1つを受け取った場合、あなたのコードはあなたがキャッチしなければならない例外をすでに処理していると思います。したがって、自分でアクセスする必要はありません。
編集:私は この分析 独自の例外をスローし、元の例外を保持する方法を見つけました。きれいな解決策はありません。
また、発生したエラーに「ラップ」する必要があることも何度もありました。
これには、関数スコープに含まれている場合と、関数内の一部の行のみをラップする場合があります。
decorator
とcontext manager
を使用するラッパーを作成しました。
import inspect
from contextlib import contextmanager, ContextDecorator
import functools
class wrap_exceptions(ContextDecorator):
def __init__(self, wrapper_exc, *wrapped_exc):
self.wrapper_exc = wrapper_exc
self.wrapped_exc = wrapped_exc
def __enter__(self):
pass
def __exit__(self, exc_type, exc_val, exc_tb):
if not exc_type:
return
try:
raise exc_val
except self.wrapped_exc:
raise self.wrapper_exc from exc_val
def __gen_wrapper(self, f, *args, **kwargs):
with self:
for res in f(*args, **kwargs):
yield res
def __call__(self, f):
@functools.wraps(f)
def wrapper(*args, **kw):
with self:
if inspect.isgeneratorfunction(f):
return self.__gen_wrapper(f, *args, **kw)
else:
return f(*args, **kw)
return wrapper
@wrap_exceptions(MyError, IndexError)
def do():
pass
do
メソッドを呼び出すときは、IndexError
を心配する必要はありません。ただMyError
try:
do()
except MyError as my_err:
pass # handle error
def do2():
print('do2')
with wrap_exceptions(MyError, IndexError):
do()
do2
内、context manager
内で、IndexError
が発生した場合、ラップされて発生しますMyError