注: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
上記のテストスクリプトを変更して、スクリプトが(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_Finalize
の pythonrun.c
function で実装されています。
PyImport_Cleanup
を呼び出して、インポートされたすべてのモジュールをファイナライズおよび割り当て解除します。この関数によって実行される最後から2番目のタスクは sys
モジュールの削除 です。これは主に _PyModule_Clear
の呼び出しで構成され、モジュールのディクショナリ内のすべてのエントリをクリアします。 -特に、stdout
やstderr
などの標準ストリームオブジェクト(Pythonオブジェクト)を含む。Py_DECREF
macro を使用します。参照カウントがゼロになったオブジェクトは、割り当て解除の対象になります。 sys
モジュールは標準ストリームオブジェクトへの最後の残りの参照を保持するため、それらの参照が_PyModule_Clear
によって設定解除されると、それらの割り当てを解除する準備が整います。1Pythonファイルオブジェクトの割り当て解除は、file_dealloc
の the 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);
}
そのメッセージを印刷した後、Pythonは PyErr_Print
を使用して例外を表示しようとします。これは PyErr_PrintEx
に委任し、その機能の一部として、PyErr_PrintEx
はsys.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);
}
不足している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この特定の動作は、前の脚注で言及した属性キャッシュメカニズムについて知らない限り、意味をなさない唯一の部分です。
今日私はこの種の問題に自分で出くわし、答えを探しに行きました。ここでの簡単な回避策は、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 | :
$
プログラムで、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