上記のテスト スクリプトを変更して、スクリプトを (Unix/ の下で) 示されているように実行したときにエラー メッセージが表示されないようにするにはどうすればよいbash
ですか?
スクリプトが標準出力に何も書き込まないようにする必要があります。つまり、すべてのprint
ステートメントと の使用、およびsys.stdout.write
それらを呼び出すすべてのコードを削除することを意味します。
これが発生する理由は、Python スクリプトからのゼロ以外の量の出力を、標準入力から決して読み取らないものにパイプしているためです。:
これはコマンドに固有のものではありません。次のような標準入力を読み取らないコマンドにパイプすることで、同じ結果を得ることができます。
python testscript.py | cd .
printer.py
または、より単純な例として、
print 'abcde'
それで
python printer.py | python printer.py
同じエラーが発生します。
あるプログラムの出力を別のプログラムにパイプすると、書き込みプログラムによって生成された出力がバッファーにバックアップされ、読み取りプログラムがバッファーからそのデータを要求するのを待ちます。バッファーが空でない限り、書き込み中のファイル オブジェクトを閉じようとすると、エラーで失敗するはずです。これが、表示されるメッセージの根本的な原因です。
try
エラーをトリガーする特定のコードは、Python の C 言語実装にあり、 /except
ブロックでキャッチできない理由を説明しています。スクリプトのコンテンツの処理が終了した後に実行されます。基本的に、Python がシャットダウンしている間、Python は を閉じようとしstdout
ますが、読み取りを待機しているバッファリングされた出力がまだあるため、失敗します。そのため、Python は通常どおりこのエラーを報告しようとしますsys.excepthook
が、ファイナライズ手順の一部として既に削除されているため、失敗します。次に、Python は にメッセージを出力しようとしますがsys.stderr
、既に割り当てが解除されているため、失敗します。画面にメッセージが表示されるのは、Python コードに不測の事態が含まれているためです。fprintf
Python の出力オブジェクトが存在しない場合でも、出力をファイル ポインタに直接書き出すことができます。
技術的な詳細
この手順の詳細に興味がある方は、 のPy_Finalize
関数に実装されている Python インタープリターのシャットダウン シーケンスを見てみましょうpythonrun.c
。
- 終了フックを呼び出してスレッドをシャットダウンした後、ファイナライズ コードは、
PyImport_Cleanup
インポートされたすべてのモジュールをファイナライズして割り当てを解除するために呼び出します。この関数によって実行される最後から 2 番目のタスクは、 moduleを削除することsys
です。これは主に、モジュールのディクショナリ内のすべてのエントリをクリアするための呼び出しで構成されます。特に、や_PyModule_Clear
などの標準ストリーム オブジェクト (Python オブジェクト) が含まれます。stdout
stderr
- 値がディクショナリから削除されるか、新しい値に置き換えられると、その参照カウントはマクロを使用して
Py_DECREF
減分されます。参照カウントがゼロになったオブジェクトは、割り当て解除の対象になります。sys
モジュールは標準ストリーム オブジェクトへの最後に残った参照を保持するため、これらの参照が によって設定解除されると_PyModule_Clear
、割り当てを解除する準備が整います。1
Python ファイル オブジェクトの割り当て解除は、のfile_dealloc
関数によって実行されfileobject.c
ます。これはまず、適切な名前のfunctionを使用して Python ファイル オブジェクトのclose
メソッドを呼び出します。close_the_file
ret = close_the_file(f);
標準のファイル オブジェクトの場合、ファイル ポインタに書き込むデータがまだある場合にエラー状態を設定するC関数にclose_the_file(f)
委譲します。fclose
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
から Python 例外プリンタへのアクセスを試みますsys.excepthook
。
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
なります。標準ストリーム オブジェクトへの最後の参照が消えるのはそのときです。参照カウントがゼロになると、すぐPy_DECREF
にオブジェクトの割り当てを解除します。しかし、主な答えにとって重要なのは、参照がモジュールの辞書から削除され、後で割り当てが解除されることだけです。sys
2繰り返しになりますが、これはsys
、属性キャッシュ メカニズムのおかげで、モジュールのディクショナリが実際に割り当てが解除される前に完全にクリアされるためです。-vv
ファイル ポインターを閉じることに関するエラー メッセージが表示される前に、すべてのモジュールの属性が設定解除されていることを確認するオプションを使用して Python を実行できます。
3この特定の動作は、前の脚注で説明した属性キャッシュ メカニズムについて知らなければ意味をなさない唯一の部分です。