質問: BrokenPipeError
を取得せずにprint()
関数にflush=True
を使用する方法はありますか?
スクリプトpipe.py
があります:
for i in range(4000):
print(i)
Unixコマンドラインから次のように呼び出します。
python3 pipe.py | head -n3000
そしてそれは戻ります:
0
1
2
このスクリプトも同様です。
import sys
for i in range(4000):
print(i)
sys.stdout.flush()
ただし、このスクリプトを実行してhead -n3000
にパイプすると:
for i in range(4000):
print(i, flush=True)
それから私はこのエラーを受け取ります:
print(i, flush=True)
BrokenPipeError: [Errno 32] Broken pipe
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored
以下の解決策も試してみましたが、BrokenPipeError
が得られます:
import sys
for i in range(4000):
try:
print(i, flush=True)
except BrokenPipeError:
sys.exit()
BrokenPipeError
は、ファントムのように正常です。これは、書き込みプロセス(python)が書き込みを試行している間に読み取りプロセス(ヘッド)が終了し、パイプの終わりを閉じるためです。
Isisは異常な状態であり、pythonスクリプトはBrokenPipeError
-より正確には、 PythonインタープリターはシステムSIGPIPEシグナルを受け取り、BrokenPipeError
をキャッチして、スクリプトがエラーを処理できるようにします。
最後の例では、例外が無視されたことを示すメッセージのみが表示されるため、エラーを効果的に処理できます-わかりました、それは真実ではありませんが、これに関連しているようです 未解決の問題 in Python:Python開発者は、異常な状態をユーザーに警告することが重要だと考えています。
本当に起こることは、例外をキャッチしても、pythonインタープリターは常にstderrでこれを通知します。しかし、メッセージを取り除くために終了する前にstderrを閉じる必要があります。
スクリプトを少し変更しました:
私が使用したスクリプトは次のとおりです。
import sys
try:
for i in range(4000):
print(i, flush=True)
except (BrokenPipeError, IOError):
print ('BrokenPipeError caught', file = sys.stderr)
print ('Done', file=sys.stderr)
sys.stderr.close()
そして、ここでpython3.3 pipe.py | head -10
の結果:
0
1
2
3
4
5
6
7
8
9
BrokenPipeError caught
Done
無関係なメッセージが必要ない場合は、次を使用します。
import sys
try:
for i in range(4000):
print(i, flush=True)
except (BrokenPipeError, IOError):
pass
sys.stderr.close()
Pythonドキュメントによると、これは次の場合にスローされます:
もう一方の端が閉じられている間にパイプに書き込もうとする
これは、headユーティリティがstdout
、その後すぐに閉じるから読み取るためです。
ご覧のとおり、すべてのsys.stdout.flush()
の後にprint()
を追加するだけで回避できます。これは、Python 3。
あるいは、このようにawk
にパイプして、head -3
と同じ結果を得ることができます。
python3 0to3.py | awk 'NR >= 4 {exit} 1'
これがお役に立てば幸いです!
投稿した出力でわかるように、最後の例外はデストラクタフェーズで発生します。そのため、最後にignored
があります。
Exception BrokenPipeError: BrokenPipeError(32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored
そのコンテキストで何が起きているかを理解するための簡単な例は次のとおりです。
>> class A():
... def __del__(self):
... raise Exception("It will be ignored!!!")
...
>>> a = A()
>>> del a
Exception Exception: Exception('It will be ignored!!!',) in <bound method A.__del__ of <__builtin__.A instance at 0x7ff1d5c06d88>> ignored
>>> a = A()
>>> import sys
>>> sys.stderr.close()
>>> del a
オブジェクトの破棄中にトリガーされるすべての例外は、発生した例外を説明する標準エラー出力を引き起こします(つまり、pythonは、破壊フェーズで何かを正しく処理できなかったことを通知します)。とにかく、その種の例外はキャッシュできないため、例外を生成したりstderr
を閉じたりできる呼び出しを削除することができます。
質問に戻ってください。その例外は実際の問題ではありません(たとえば無視されます)が、印刷したくない場合は、オブジェクトが破棄されるかstderr
を@SergeBallestaとして閉じるときに呼び出すことができる関数をオーバーライドする必要があります正しい提案:あなたの場合はshutdownwrite
およびflush
関数であり、破壊コンテキストで例外はトリガーされません
それはあなたがそれを行う方法の例です:
import sys
def _void_f(*args,**kwargs):
pass
for i in range(4000):
try:
print(i,flush=True)
except (BrokenPipeError, IOError):
sys.stdout.write = _void_f
sys.stdout.flush = _void_f
sys.exit()
これらのシグナルハンドラを抑制するコマンドラインオプションがあることをしばしば望んでいました。
import signal
# Don't turn these signal into exceptions, just die.
signal.signal(signal.SIGINT, signal.SIG_DFL)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
代わりに、できることはPythonスクリプトの実行が開始されるとすぐにハンドラーをアンインストールすることです。
一時的にSIGPPIEを無視する
これがどれほど悪いアイデアかはわかりませんが、うまくいきます:
#!/usr/bin/env python3
import signal
import sys
sigpipe_old = signal.getsignal(signal.SIGPIPE)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
for i in range(4000):
print(i, flush=True)
signal.signal(signal.SIGPIPE, sigpipe_old)
他の人が根本的な問題を詳細にカバーしている間、簡単な回避策があります:
python whatever.py | tail -n +1 | head -n3000
説明:tail
は、STDINが閉じられるまでバッファリングします(pythonは終了し、STDOUTを閉じます)。そのため、ヘッドが終了するとテールのみがSIGPIPEを取得します。 -n +1
は実質的にノーオペレーションであり、テール出力を1行目から始まる「テール」にします。これはバッファ全体です。