web-dev-qa-db-ja.com

Pythonでフラッシュを行うときにBrokenPipeErrorを防ぐ方法は?

質問: 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()
24

BrokenPipeErrorは、ファントムのように正常です。これは、書き込みプロセス(python)が書き込みを試行している間に読み取りプロセス(ヘッド)が終了し、パイプの終わりを閉じるためです。

Isisは異常な状態であり、pythonスクリプトはBrokenPipeError-より正確には、 PythonインタープリターはシステムSIGPIPEシグナルを受け取り、BrokenPipeErrorをキャッチして、スクリプトがエラーを処理できるようにします。

最後の例では、例外が無視されたことを示すメッセージのみが表示されるため、エラーを効果的に処理できます-わかりました、それは真実ではありませんが、これに関連しているようです 未解決の問題 in Python:Python開発者は、異常な状態をユーザーに警告することが重要だと考えています。

本当に起こることは、例外をキャッチしても、pythonインタープリターは常にstderrでこれを通知します。しかし、メッセージを取り除くために終了する前にstderrを閉じる必要があります。

スクリプトを少し変更しました:

  • 最後の例で行ったようにエラーをキャッチします
  • iOError(Windows64のPython34で取得)またはBrokenPipeError(FreeBSD 9.0のPython 33)のいずれか)をキャッチし、そのためのメッセージを表示します
  • stderrでカスタムDoneメッセージを表示します(パイプが壊れているため、stdoutは閉じられます)
  • メッセージを取り除くために終了する前に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()
25
Serge Ballesta

Pythonドキュメントによると、これは次の場合にスローされます:

もう一方の端が閉じられている間にパイプに書き込もうとする

これは、headユーティリティがstdoutその後すぐに閉じるから読み取るためです。

ご覧のとおり、すべてのsys.stdout.flush()の後にprint()を追加するだけで回避できます。これは、Python 3。

あるいは、このようにawkにパイプして、head -3と同じ結果を得ることができます。

python3 0to3.py | awk 'NR >= 4 {exit} 1'

これがお役に立てば幸いです!

4
phantom

投稿した出力でわかるように、最後の例外はデストラクタフェーズで発生します。そのため、最後に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()
1
Michele d'Amico

これらのシグナルハンドラを抑制するコマンドラインオプションがあることをしばしば望んでいました。

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スクリプトの実行が開始されるとすぐにハンドラーをアンインストールすることです。

0
Waxrat

一時的に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行目から始まる「テール」にします。これはバッファ全体です。

0
notpeter