0

重複の可能性:
例外が発生したときに Python トレースバック オブジェクトを変更するにはどうすればよいですか?

次のおもちゃの例を考えてみましょう。

def twice(n):
    _validate_twice_args(n)
    return 2*n

def _validate_twice_args(n):
    if type(n) != int:
        raise TypeError('n must be an int')

twice(None)
--
Traceback (most recent call last):
  File "demo.py", line 9, in <module>
    twice(None)
  File "demo.py", line 2, in twice
    _validate_twice_args(n)
  File "demo.py", line 7, in _validate_twice_args
    raise TypeError('n must be an int')
TypeError: n must be an int

エラーの場所は call ですがtwice(None)、トレースバックは、エラーの責任者が気付いていないコードを参照しています (「_validate誰?私はそれを呼び出したことがない!私はそれが何であるかさえ知りません!」)。 、「API の背後にある」べきコ​​ードを不必要に公開します。 のような「プライベート」ヘルパー関数がない場合でも、_validate_twice_args不正な引数に応答して出力されるスタック トレースは、内部コードを不必要に公開し、エラーの場所を覆い隠します。

たとえば、 のコードをインライン化する_validate_twice_argsと、スタック トレースは次のようになります。

Traceback (most recent call last):
  File "demo.py", line 10, in <module>
    twice(None)
  File "demo.py", line 3, in twice
    raise TypeError('n must be an int')
TypeError: n must be an int

この種のエラーのスタック トレースがどのように表示されるかを理解するために非常によく似たタイプのエラー ( のtwice()代わりに呼び出しtwice(None)が行われたもの) から発生したものを次に示しますが、制御が に渡される前に Python によって発生したものですtwice

Traceback (most recent call last):
  File "demo.py", line 9, in <module>
    twice()
TypeError: twice() takes exactly 1 argument (0 given)

この場合も、twice無効な引数リストを使用した の呼び出しにエラーがあります。したがって、スタック トレースはエラーの場所を直接指しています (そして、エラーが最初に検出された基になる [おそらく C] コードの行ではありません。ありがたいことに)。これはあるべき姿です、IMO 1 .

スタックトレースの最後の行がバグのある呼び出しのソースコード行を参照するように変更するtwice_argsにはどうすればよいですか?_validate_twice_argstwice


1そのような場合にスタックトレースが何を言うべきかについての意見が私の意見と異なることは承知していますが、これらの哲学的な考慮事項はこのスレッドの主題ではありません。

編集:元の投稿の発言を強調しましたが、最初の 2 人の回答者は明らかに見落としていました。

4

4 に答える 4

3

さて、これに変更できますtwice

def twice(n):
    try:
        _validate_twice_args(n)
    except Exception, e:
        raise e
    return 2*n

ただし、もちろん、try/except ブロックを に入れる場合twiceは、実際のチェックをそこで行うこともできます。もちろん、脚注にもかかわらず、そこで例外を発生させたい場合は、これが本当にすべきことです。発生した場所で例外が発生します。関数を呼び出して、その特定の関数の実際の本体で例外が発生することを期待することはできません。関数は常に他の関数を呼び出します。そのようなことを行うバリデーター関数を使用することは問題ありませんが、そうである場合は、例外がそこで発生し、他の場所では発生しないことを受け入れる必要があります。

あなたの編集への対応: 例外が発生したときに「プライベート コードを公開」しているように見えるかもしれませんが、別の方法でそれを行うと、バグが隠されます。トレースバックを使用すると、スタックを簡単に調べて、関数呼び出した場所を探すことができます。ただし、説明したことを行うと、トレースバックが切り詰められすぎて、エラーが実際に発生した場所を確認できなくなります。コードが別の場所で例外を発生させることができれば、バグのあるコードは非常に簡単に隠れることができます。

より一般的には、「エラーの軌跡」とあなたが説明するものは、赤いニシンです。例外(「エラー」である場合もそうでない場合もある) は、例外条件が検出された時点で発生する必要があります。「エラーの場所」はより複雑な質問であり、別の場所で発生する「スパゲッティ例外」を作成して紙に書き留めようとするべきではありません。人間としては、この特定の呼び出しチェーンで「間違いが発生した場所」であることがわかりますがtwice(None)、それは、プログラムを賢くして、それを理解させるようにする必要があるという意味ではありません。ある時点でうまくいかず、より大きな混乱を引き起こす可能性があります。

だからあなたが強調したことに答えるために:いいえ、あなたはそれをすることはできません. 発生した場所で例外が発生します。ある場所で例外を発生させて、別の場所で発生したかのように表示させることはできません。

于 2012-09-12T03:57:07.503 に答える
1

長い答えの短い: C 拡張機能を記述したり、ctypes マジックを使用したりせずに、通常の Python トレースバックを操作して、コード内のステートメント以外の場所からエラーが発生したふりをすることはできません。raise

Python から発生した例外raiseには、トレースバックの一番上にステートメントがあります。これは、定義上、例外が発生した場所であるためです。これを変更することはできません (前述のトリッキーなしでは)。あなたの場合、例外が関数呼び出し (最上位フレームとして) から来たように見えるようにしたいのですが、純粋な Python からは不可能だと言っています。

これを行う ctypes または CPython の方法をまだ見たい場合は、お見せできます。しかし、純粋な Python コードに固執することによって、あなたが望むものを手に入れることはできません (そして明らかにあなたが望むものを望んでいます)。

編集: OK、これは 100% 正確ではありません。もちろん、例外をスローする拡張メソッドをいつでも呼び出すことができます。例外は、拡張メソッドの呼び出しから「発生します」。したがって、twice拡張メソッドとして記述した場合は、必要な正確なセマンティクスを実装できます (これが、「C 拡張を記述する」という意味です)。

の存在により、ジェネレーター関数またはステートメントgenerator.throw()の行から例外が発生したように見えるようにすることもできます。これらはすべて Python コードからのものです。もちろん、どちらも必要なセマンティクスを実装するのに役立ちませんが、完全を期すためにこれらについて言及したいと思います。defyield

于 2012-09-12T04:40:25.623 に答える
1

例外を発生させるときに Python トレースバック オブジェクトを変更するにはどうすればよいですか? を参照してください。

そこには、これをしないようにするためのアドバイスと、本当に必要な場合の方法を示す jinja2 の醜いコードへのリンクがあります。

于 2012-09-12T05:06:24.010 に答える
0

コードを次のように変更できます。

def twice(n):
    error = _validate_twice_args(n)
    if error:
        raise error
    return 2*n

def _validate_twice_args(n):
    if type(n) != int:
        return TypeError('n must be an int')
    return None

twice(None)

このように、からエラーが発生しtwiceます。twice残念ながら、単純なメソッド呼び出しよりもメソッドに少し多くのコードが必要です。

Traceback (most recent call last):
  File "<module1>", line 28, in <module>
  File "<module1>", line 25, in main
  File "<module1>", line 16, in twice
TypeError: n must be an int
于 2012-09-12T03:56:45.773 に答える