web-dev-qa-db-ja.com

「sys.excepthookが見つかりません」エラーを黙らせる方法は?

注:Windowsで以下に説明する問題、またはPython 2.7.3以外のバージョン)で説明した問題を再現しようとしませんでした。

問題のある問題を引き出す最も確実な方法は、次のテストスクリプトの出力を:bashの下):

try:
    for n in range(20):
        print n
except:
    pass

つまり:

% python testscript.py | :
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

私の質問は:

上記のテストスクリプトを変更するにはどうすればよいですか(Unix/bashの下で)スクリプトを実行したときにエラーメッセージが表示されないようにするには?

(テストスクリプトが示すように、エラーはtry-except。)

確かに上記の例は非常に人工的なものですが、同じ問題に直面しています時々私のスクリプトの出力がサードパーティのソフトウェアを介してパイプされるとき。

エラーメッセージは確かに無害ですが、エンドユーザーを当惑させているので、黙らせたいと思います。

編集:次のスクリプトは、sys.excepthookを再定義するという点でのみ上記の元のスクリプトと異なり、上記のスクリプトとまったく同じように動作します。

import sys
STDERR = sys.stderr
def excepthook(*args):
    print >> STDERR, 'caught'
    print >> STDERR, args

sys.excepthook = excepthook

try:
    for n in range(20):
        print n
except:
    pass
44
kjo

上記のテストスクリプトを変更して、スクリプトが(Unix/bashの下で)示されているように実行されるときのエラーメッセージを回避するにはどうすればよいですか。

スクリプトが標準出力に何も書き込まないようにする必要があります。つまり、printステートメントとsys.stdout.writeの使用、およびそれらを呼び出すコードを削除することを意味します。

これが発生する理由は、Pythonスクリプトからゼロ以外の量の出力を標準入力から読み取らないものにパイプしているためです。これは:コマンドに固有のものではありません。次のような標準入力を読み取らないコマンドにパイプすることで、同じ結果を得ることができます。

python testscript.py | cd .

または、より簡単な例として、次のものを含むスクリプトprinter.pyを考えてください。

print 'abcde'

それから

python printer.py | python printer.py

同じエラーが発生します。

あるプログラムの出力を別のプログラムにパイプすると、書き込みプログラムによって生成された出力はバッファーにバックアップされ、読み取りプログラムがバッファーからそのデータを要求するのを待ちます。バッファが空でない限り、書き込みファイルオブジェクトを閉じようとすると、エラーが発生して失敗します。これが、表示されているメッセージの根本的な原因です。

エラーを引き起こす特定のコードはPythonのC言語実装にあります。これは、try/exceptブロックでキャッチできない理由を説明します。スクリプトの内容が終了した後に実行されます。処理。基本的に、Pythonは自身をシャットダウンしている間、stdoutを閉じようとしますが、読み取りを待機しているバッファ出力がまだあるため失敗します。 Pythonは通常どおりこのエラーを報告しようとしますが、sys.excepthookはファイナライズ手順の一部として既に削除されているため、失敗します。 Pythonはsys.stderrにメッセージを出力しようとしますが、すでに割り当て解除されているため、失敗します。画面にメッセージが表示される理由は、Pythonの出力オブジェクトが存在しなくても、Pythonコードに偶発性fprintfが含まれており、ファイルポインターに出力を直接書き込むためです。

技術的な詳細

この手順の詳細に興味がある人のために、Pythonインタープリターのシャットダウンシーケンスを見てみましょう。これは、Py_Finalizepythonrun.c function で実装されています。

  1. 終了フックを呼び出してスレッドをシャットダウンした後、ファイナライズコードは PyImport_Cleanup を呼び出して、インポートされたすべてのモジュールをファイナライズおよび割り当て解除します。この関数によって実行される最後から2番目のタスクは sysモジュールの削除 です。これは主に _PyModule_Clear の呼び出しで構成され、モジュールのディクショナリ内のすべてのエントリをクリアします。 -特に、stdoutstderrなどの標準ストリームオブジェクト(Pythonオブジェクト)を含む。
  2. 値がディクショナリから削除されるか、新しい値に置き換えられると、 その参照カウントは減少しますthe Py_DECREF macro を使用します。参照カウントがゼロになったオブジェクトは、割り当て解除の対象になります。 sysモジュールは標準ストリームオブジェクトへの最後の残りの参照を保持するため、それらの参照が_PyModule_Clearによって設定解除されると、それらの割り当てを解除する準備が整います。1
  3. Pythonファイルオブジェクトの割り当て解除は、file_deallocthe fileobject.c function によって行われます。最初の Pythonファイルオブジェクトのcloseメソッドを呼び出す 適切な名前の close_the_file function を使用して:

    ret = close_the_file(f);
    

    標準ファイルオブジェクトの場合、close_the_file(f)C fclose functionに委任します 。ファイルポインターに書き込むデータがまだある場合にエラー条件を設定します。 file_deallocはそのエラー状態をチェックし、最初に表示されるメッセージを出力します:

    if (!ret) {
        PySys_WriteStderr("close failed in file object destructor:\n");
        PyErr_Print();
    }
    else {
        Py_DECREF(ret);
    }
    
  4. そのメッセージを印刷した後、Pythonは PyErr_Print を使用して例外を表示しようとします。これは PyErr_PrintEx に委任し、その機能の一部として、PyErr_PrintExsys.excepthookからPython例外プリンターにアクセスしようとします。

    hook = PySys_GetObject("excepthook");
    

    Pythonプログラムの通常の過程でこれを行うと問題はありませんが、この状況ではsys.excepthookはすでにクリアされています。2 Pythonはこのエラー状態をチェックし、2番目のメッセージを通知として出力します。

    if (hook && hook != Py_None) {
        ...
    } else {
        PySys_WriteStderr("sys.excepthook is missing\n");
        PyErr_Display(exception, v, tb);
    }
    
  5. 不足しているexcepthookについて通知した後、Pythonは、スタックトレースを表示するためのデフォルトの方法である PyErr_Display を使用して例外情報の出力にフォールバックします。この関数が最初に行うことは、sys.stderrにアクセスしようとすることです。

    PyObject *f = PySys_GetObject("stderr");
    

    この場合、sys.stderrはすでにクリアされており、アクセスできないため、機能しません。3 したがって、コードはfprintfを直接呼び出して、3番目のメッセージをC標準エラーストリームに送信します。

    if (f == NULL || f == Py_None)
        fprintf(stderr, "lost sys.stderr\n");
    

興味深いことに、動作はPython 3.4+では少し異なります。これは、組み込みモジュールがクリアされる前に、ファイナライズ手順が 標準出力とエラーストリームを明示的にフラッシュする になったためです。このように、データの書き込みを待機している場合、通常のファイナライズ手順での「偶発的な」障害ではなく、その状態を明示的に示すエラーが発生します。また、実行する場合

python printer.py | python printer.py

Python 3.4を使用して(もちろんprintステートメントに括弧を付けた後)、エラーはまったく発生しません。 Pythonの2回目の呼び出しは、何らかの理由で標準入力を消費する可能性がありますが、それはまったく別の問題です。


1実際、それは嘘です。 Pythonのインポートメカニズム インポートされた各モジュールの辞書のコピーをキャッシュする 。これは _PyImport_Fini が実行されるまでリリースされません。 Py_Finalize および thats 標準ストリームオブジェクトへの最後の参照が消えたとき。参照カウントがゼロに達すると、Py_DECREFはオブジェクトの割り当てを解除します immediately 。しかし、主な答えで重要なのは、参照がsysモジュールの辞書から削除され、しばらくしてから割り当てが解除されることです。

2繰り返しますが、これは、属性キャッシングメカニズムのおかげで、sysモジュールのディクショナリが完全にクリアされてから、実際に割り当てが解除されるためです。 -vvオプションを指定してPythonを実行すると、ファイルポインターを閉じることに関するエラーメッセージが表示される前に、設定されていないすべてのモジュールの属性を確認できます。

3この特定の動作は、前の脚注で言及した属性キャッシュメカニズムについて知らない限り、意味をなさない唯一の部分です。

66
David Z

今日私はこの種の問題に自分で出くわし、答えを探しに行きました。ここでの簡単な回避策は、stdioを最初にフラッシュすることです。したがって、スクリプトのシャットダウン中に失敗する代わりにpythonブロック。

--- a/testscript.py
+++ b/testscript.py
@@ -9,5 +9,6 @@ sys.excepthook = excepthook
 try:
     for n in range(20):
         print n
+    sys.stdout.flush()
 except:
     pass

次に、このスクリプトでは、例外(IOError:[Errno 32] Broken pipe)がtry ... exceptによって抑制されるため、何も起こりません。

$ python testscript.py  | :
$
11
Andrew

プログラムで、try/exceptブロックを使用してキャッチできない例外をスローします。彼をキャッチするには、関数sys.excepthook

import sys
sys.excepthook = lambda *args: None

ドキュメント から:

sys.excepthook(type、value、traceback)

例外が発生してキャッチされなかった場合、インタープリターは3つの引数、例外クラス、例外インスタンス、およびトレースバックオブジェクトを指定してsys.excepthookを呼び出します。対話型セッションでは、これは制御がプロンプトに戻る直前に発生します。 Pythonプログラムでは、これはプログラムが終了する直前に発生します。このようなトップレベルの例外の処理は、sys.excepthookに別の3つの引数関数を割り当てることでカスタマイズできます。

実例:

import sys
import logging

def log_uncaught_exceptions(exception_type, exception, tb):

    logging.critical(''.join(traceback.format_tb(tb)))
    logging.critical('{0}: {1}'.format(exception_type, exception))

sys.excepthook = log_uncaught_exceptions
2
defuz