Pythonの「内部例外」(トレースバックあり)?


146

私のバックグラウンドはC#で、最近Pythonでプログラミングを始めました。例外がスローされたときは、通常、完全なスタックトレースを表示しながら、さらに情報を追加する別の例外にラップします。C#では非常に簡単ですが、Pythonではどうすればよいですか?

例えば。C#では、次のようにします。

try
{
  ProcessFile(filePath);
}
catch (Exception ex)
{
  throw new ApplicationException("Failed to process file " + filePath, ex);
}

Pythonでも同様のことができます。

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file ' + filePath, e)

...しかし、これは内部例外のトレースバックを失います!

編集:両方の例外メッセージと両方のスタックトレースを確認し、2つを関連付けます。つまり、C#の場合と同様に、ここで例外Xが発生し、次に例外Yが発生したことを出力で確認したいと思います。これはPython 2.6で可能ですか?(Glenn Maynardの答えに基づく)私がこれまでにできる最善のように見えます:

try:
  ProcessFile(filePath)
except Exception as e:
  raise Exception('Failed to process file' + filePath, e), None, sys.exc_info()[2]

これには、メッセージとトレースバックの両方が含まれますが、トレースバックのどこで発生した例外かは示されません。


3
受け入れられた回答は古くなっています。おそらく別の回答を受け入れることを検討する必要があります。
アーロンホール

1
@AaronHall残念ながらOPは2015
。– Antti Haapala

回答:


136

Python 2

それは簡単です; トレースバックを3番目の引数として渡します。

import sys
class MyException(Exception): pass

try:
    raise TypeError("test")
except TypeError, e:
    raise MyException(), None, sys.exc_info()[2]

1つの例外をキャッチして別の例外を再発生させる場合は、常にこれを行ってください。


4
ありがとう。トレースバックは保持されますが、元の例外のエラーメッセージは失われます。両方のメッセージと両方のトレースバックを表示するにはどうすればよいですか?
EMP

6
raise MyException(str(e)), ...など
グレン・メイナード

23
Pythonの3が追加されますraise E() from tb.with_traceback(...)
ディマTisnek

3
@GlennMaynardこれはかなり古い質問ですが、の中央の引数はraise例外に渡す値です(最初の引数がインスタンスではなく例外クラスの場合)。したがって、例外を交換する場合は、を実行する代わりにraise MyException(str(e)), None, sys.exc_info()[2]、これを使用することをお勧めしますraise MyException, e.args, sys.exc_info()[2]
bgusach

8
Python2および3に準拠した方法は、将来のパッケージを使用して可能です:python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_およびraise_(ValueError, None, sys.exc_info()[2])
jtpereyda 2016

239

Python 3

Python 3では、次のことができます。

try:
    raise MyExceptionToBeWrapped("I have twisted my ankle")

except MyExceptionToBeWrapped as e:

    raise MyWrapperException("I'm not in a good shape") from e

これは次のようなものを生成します:

   Traceback (most recent call last):
   ...
   MyExceptionToBeWrapped: ("I have twisted my ankle")

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

   Traceback (most recent call last):
   ...
   MyWrapperException: ("I'm not in a good shape")

17
raise ... from ...確かにこれはPython 3でこれを行う正しい方法です。これにはより多くの投票が必要です。
Nakedible

Nakedible私は残念ながら、ほとんどの人がまだのPython 3を使用していないので、それだと思う
ティムLudwinski

これはpython 3で 'from'を使用しても発生するようです
Steve Vermeulen

Python 2にバックポートできる可能性があります。
Marcin Wojnarski、2015

4
@ogrisel futureこれを達成するためにパッケージを使用できます:python-future.org/compatible_idioms.html#raising-exceptions Eg from future.utils import raise_およびraise_(ValueError, None, sys.exc_info()[2])
jtpereyda 2016

19

Python 3には、例外をチェーンするraise... fromがあります。Glennの答えはPython 2.7に最適ですが、元の例外のトレースバックのみを使用し、エラーメッセージやその他の詳細を破棄します。以下は、現在のスコープのコンテキスト情報を元の例外のエラーメッセージに追加し、その他の詳細はそのままにしておくPython 2.7の例です。

既知の例外タイプ

try:
    sock_common = xmlrpclib.ServerProxy(rpc_url+'/common')
    self.user_id = sock_common.login(self.dbname, username, self.pwd)
except IOError:
    _, ex, traceback = sys.exc_info()
    message = "Connecting to '%s': %s." % (config['connection'],
                                           ex.strerror)
    raise IOError, (ex.errno, message), traceback

このフレーバーのraiseステートメントは、例外タイプを最初の式、タプルの例外クラスコンストラクター引数を2番目の式、トレースバックを3番目の式として受け取ります。Python 2.2より前のバージョンを実行している場合は、の警告を参照してくださいsys.exc_info()

例外タイプ

次に、コードがキャッチする必要のある例外の種類がわからない場合のより一般的な目的の例を示します。欠点は、例外の型がなくなり、RuntimeErrorが発生するだけです。tracebackモジュールをインポートする必要があります。

except Exception:
    extype, ex, tb = sys.exc_info()
    formatted = traceback.format_exception_only(extype, ex)[-1]
    message = "Importing row %d, %s" % (rownum, formatted)
    raise RuntimeError, message, tb

メッセージを変更する

例外タイプでコンテキストを追加できる場合の別のオプションを次に示します。例外のメッセージを変更してから、再度発生させることができます。

import subprocess

try:
    final_args = ['lsx', '/home']
    s = subprocess.check_output(final_args)
except OSError as ex:
    ex.strerror += ' for command {}'.format(final_args)
    raise

これにより、次のスタックトレースが生成されます。

Traceback (most recent call last):
  File "/mnt/data/don/workspace/scratch/scratch.py", line 5, in <module>
    s = subprocess.check_output(final_args)
  File "/usr/lib/python2.7/subprocess.py", line 566, in check_output
    process = Popen(stdout=PIPE, *popenargs, **kwargs)
  File "/usr/lib/python2.7/subprocess.py", line 710, in __init__
    errread, errwrite)
  File "/usr/lib/python2.7/subprocess.py", line 1327, in _execute_child
    raise child_exception
OSError: [Errno 2] No such file or directory for command ['lsx', '/home']

check_output()呼び出された行が表示されていることがわかりますが、例外メッセージにはコマンドラインが含まれています。


1
どこex.strerrorから来たの?Pythonのドキュメントでは、それに関連するヒットを見つけることができません。そうじゃないのstr(ex)
Henrik Heimbuerger 2013

1
IOErrorEnvironmentErrorerrornoおよびstrerror属性を提供する@hheimbuerger から派生しています。
Don Kirkby 2013

どのように私はキャッチErrorすることRuntimeErrorによって任意に、例えばValueError をラップしExceptionますか?この場合の回答を再現すると、スタックトレースが失われます。
カールリヒター

@karlさん、何を聞いているのかわかりません。新しい質問にサンプルを投稿して、ここからリンクできますか?
Don Kirkby

stackoverflow.com/questions/23157766/…でOPの質問の複製を編集しましたが、回答を直接考慮に入れて明確にしました。私たちはそこで話し合うべきです:)
Karl Richter

12

Python 3.xの

raise Exception('Failed to process file ' + filePath).with_traceback(e.__traceback__)

または単に

except Exception:
    raise MyException()

伝播されますMyExceptionが、処理されない場合は両方の例外が出力されます。

Python 2.xの

raise Exception, 'Failed to process file ' + filePath, e

__context__属性を強制終了することで、両方の例外の出力を防ぐことができます。ここで、それを使用して例外を即座にキャッチして変更するコンテキストマネージャーを記述します(それらがどのように機能するかの説明については、http://docs.python.org/3.1/library/stdtypes.htmlを参照してください)。

try: # Wrap the whole program into the block that will kill __context__.

    class Catcher(Exception):
        '''This context manager reraises an exception under a different name.'''

        def __init__(self, name):
            super().__init__('Failed to process code in {!r}'.format(name))

        def __enter__(self):
            return self

        def __exit__(self, exc_type, exc_val, exc_tb):
            if exc_type is not None:
                self.__traceback__ = exc_tb
                raise self

    ...


    with Catcher('class definition'):
        class a:
            def spam(self):
                # not really pass, but you get the idea
                pass

            lut = [1,
                   3,
                   17,
                   [12,34],
                   5,
                   _spam]


        assert a().lut[-1] == a.spam

    ...


except Catcher as e:
    e.__context__ = None
    raise

4
TypeError:raise:arg 3はトレースバックまたはNoneでなければなりません
Glenn Maynard

申し訳ありませんが、間違いを犯しました。どういうわけか、例外も受け入れ、トレースバック属性を自動的に取得すると思いました。あたりとしてdocs.python.org/3.1/reference/...、これは電子.__ traceback__する必要があります
イリヤのn。

1
@ilyan .: Python 2にはe.__traceback__属性がありません!
Jan Hudec 2013年

5

私はあなたがPython 2.xでこれを行うことができるとは思いませんが、この機能に似たものはPython 3の一部です。PEP3134から:

今日のPython実装では、例外は3つの部分(タイプ、値、およびトレースバック)で構成されています。'sys'モジュールは、exc_type、exc_value、exc_tracebackの3つの並列変数で現在の例外を公開し、sys.exc_info()関数はこれらの3つの部分のタプルを返し、 'raise'ステートメントは3つの引数形式を受け入れますこれらの3つの部分。多くの場合、例外を操作するには、これら3つを並行して渡す必要があり、面倒でエラーが発生しやすくなります。さらに、「except」ステートメントは、トレースバックではなく、値へのアクセスのみを提供できます。' traceback '属性を例外値に追加すると、すべての例外情報に1つの場所からアクセスできるようになります。

C#との比較:

C#の例外には、別の例外を指す可能性がある読み取り専用の 'InnerException'プロパティが含まれています。そのドキュメント[10]は、「前の例外Yの直接の結果として例外Xがスローされた場合、XのInnerExceptionプロパティにはYへの参照が含まれている必要がある」と述べています。このプロパティはVMによって自動的には設定されません。むしろ、すべての例外コンストラクターは、オプションの 'innerException'引数を受け取って明示的に設定します。' cause '属性はInnerExceptionと同じ目的を果たしますが、このPEPはすべての例外のコンストラクターを拡張するのではなく、新しい形式の 'raise'を提案します。C#には、InnerExceptionチェーンの最後に直接ジャンプするGetBaseExceptionメソッドも用意されています。

また、Java、Ruby、Perl 5はこのタイプのものをサポートしていないことにも注意してください。もう一度引用:

他の言語については、「catch」/「rescue」または「finally」/「ensure」句で別の例外が発生すると、JavaとRubyの両方が元の例外を破棄します。Perl 5には組み込みの構造化例外処理がありません。Perl 6の場合、RFC番号88 [9]は、@@という名前の配列に連鎖例外を暗黙的に保持する例外メカニズムを提案します。


しかし、もちろん、Perl5では、「confess qq {OH NOES!$ @}」と言うだけで、他の例外のスタックトレースを失うことはありません。または、例外を保持する独自のタイプを実装することもできます。
jrockway

4

Python 2と3の間の最大の互換性のためraise_fromに、sixライブラリで使用できます。 https://six.readthedocs.io/#six.raise_from。ここにあなたの例があります(明確にするために少し変更されています):

import six

try:
  ProcessFile(filePath)
except Exception as e:
  six.raise_from(IOError('Failed to process file ' + repr(filePath)), e)

3

私のCausedExceptionクラスを使用して、Python 2.xで例外を連鎖させることができます(Python 3でも、新しく発生した例外の原因として複数のキャッチされた例外を与えたい場合に役立ちます)。多分それはあなたを助けることができます。


2

多分あなたは関連する情報をつかんでそれを渡すことができますか?私は次のようなものを考えています:

import traceback
import sys
import StringIO

class ApplicationError:
    def __init__(self, value, e):
        s = StringIO.StringIO()
        traceback.print_exc(file=s)
        self.value = (value, s.getvalue())

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

try:
    try:
        a = 1/0
    except Exception, e:
        raise ApplicationError("Failed to process file", e)
except Exception, e:
    print e

2

仮定:

  • Python 2で機能するソリューションが必要です(純粋なPython 3の場合はraise ... fromソリューションを参照)
  • エラーメッセージを充実させたい、たとえば追加のコンテキストを提供したい
  • 完全なスタックトレースが必要

docs https://docs.python.org/3/tutorial/errors.html#raising-exceptionsの簡単なソリューションを使用できます

try:
    raise NameError('HiThere')
except NameError:
    print 'An exception flew by!' # print or log, provide details about context
    raise # reraise the original exception, keeping full stack trace

出力:

An exception flew by!
Traceback (most recent call last):
  File "<stdin>", line 2, in ?
NameError: HiThere

重要なのは、スタンドアロンの単純化された「raise」キーワードです。これにより、exceptブロックで例外が再発生します。


これはPython 2&3互換ソリューションです!ありがとう!
Andy Chase

別の種類の例外を発生させるという考えだったと思います。
Tim Ludwinski、2013

2
これはネストされた例外の連鎖ではなく、1つの例外を再呼び出しするだけです
Karl Richter 14

例外メッセージを充実させて完全なスタックトレースを取得する必要がある場合は、これがpython 2の最良のソリューションです。
geekQ 2017

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