15

Python には既知の問題があり、stdout で「パイプの破損」が発生すると「ファイル オブジェクト デストラクタでクローズに失敗しました」という問題があります - Python トラッカーの問題 11380 ; Pythonでも見られる- 私の Python3 スクリプトは、出力を先頭または末尾 (sys モジュール) にパイプするのをためらうのはなぜですか? - スタック オーバーフロー

私がやりたいのは、Python 2.7 と Python 3+ の両方で、この問題が発生したときに同じカスタム メッセージを出力することです。そこで、テスト スクリプトを準備して実行します ( Ubuntu 11.04testprint.pyで実行されたスニペットを示します)。bash

$ cat > testprint.py <<"EOF"
import sys

def main():
  teststr = "Hello " * 5
  sys.stdout.write(teststr + "\n")

if __name__ == "__main__":
  main()
EOF

$ python2.7 testprint.py 
Hello Hello Hello Hello Hello 

$ python2.7 testprint.py | echo

close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr

$ python3.2 testprint.py | echo

Exception IOError: (32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

上記のリンクから予想されるように、2 つの異なるメッセージがあります。Help with a pipe error (velocityreviews.com)では、 sys.stdout.flush()Python 2 にそのメッセージの代わりに IOError を強制的に登録させるために使用することをお勧めします。これで、次のようになります。

$ cat > testprint.py <<"EOF"
import sys

def main():
  teststr = "Hello " * 5
  sys.stdout.write(teststr + "\n")
  sys.stdout.flush()

if __name__ == "__main__":
  main()
EOF

$ python2.7 testprint.py | echo

Traceback (most recent call last):
  File "testprint.py", line 9, in <module>
    main()
  File "testprint.py", line 6, in main
    sys.stdout.flush()
IOError: [Errno 32] Broken pipe

$ python3.2 testprint.py | echo

Traceback (most recent call last):
  File "testprint.py", line 9, in <module>
    main()
  File "testprint.py", line 6, in main
    sys.stdout.flush()
IOError: [Errno 32] Broken pipe
Exception IOError: (32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

さて、これらの例外を「無視」する (または、私の場合は、カスタム エラー メッセージに置き換える) 方法は、それらを処理することです。

例外を無視する - comp.lang.python

> [インタープリター] に例外を無視させる方法はありますか?
いいえ。例外を処理するか、例外を生成しないコードを記述してください。

... そして、Python の紹介 - 例外の処理のメモのように、それを行う方法は try/except ブロックです。それでは、それを試してみましょう:

$ cat > testprint.py <<"EOF"
import sys

def main():
  teststr = "Hello " * 5
  try:
    sys.stdout.write(teststr + "\n")
    sys.stdout.flush()
  except IOError:
    sys.stderr.write("Exc: " + str(sys.exc_info()[0]) + "\n")

if __name__ == "__main__":
  main()
EOF

$ python2.7 testprint.py | echo

Exc: <type 'exceptions.IOError'>

$ python3.2 testprint.py | echo

Exc: <class 'IOError'>
Exception IOError: (32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

OK、try/except は Python 2.7 で期待どおりに動作しますが、Python 3.2 は両方とも期待どおりに処理しメッセージを生成しException ... ignoredます! 何が問題なのですか? " except IOError" では Python 3 には十分ではありませんか? しかし、そうでなければなりませんExc:...

では、ここでの問題は何Exception ... ignoredですか。また、例外を処理しているにもかかわらず、Python 3 でまだ印刷されているのはなぜですか? さらに重要なことは、印刷されないException ... ignored ようにするにはどうすればよいですか?

4

3 に答える 3

4

これに関するいくつかの注意事項-問題はまだ解決されていません...最初に:

問題 6294: シャットダウン例外無視メッセージの改善 - Python トラッカー

このエラー メッセージは、__del__ メソッドを含む多くのコンテキストから呼び出される PyErr_WriteUnraisable で生成されます。シャットダウン中に呼び出された __del__ メソッドは、あなたが話しているエラーを生成している可能性が最も高いですが、私が知る限り、 __del__ メソッドは特にシャットダウン中に呼び出されていることを知る方法がありません. したがって、提案されたメッセージの修正は機能しません。[....]
ただし、これはトラップすることもできないメッセージであるため、変更しても完全に安全なはずです。

さて、トラップできないこのメッセージをありがとう、非常に便利です。これはIgnore exceptions print to stderr in del () - Stack Overflowに何らかの形で関連していると思いますが、その投稿では (明らかに) カスタム__del__メソッドについて説明しています。

次のリソースを少し使用します。

...スクリプトを変更したので、可能なすべてのハンドラーをオーバーロードして、この例外を「処理」できるスペースがないかどうかを確認し、「無視」されないようにします。

import sys
import atexit
import signal
import inspect, pprint

def signalPIPE_handler(signal, frame):
    sys.stderr.write('signalPIPE_handler!'+str(sys.exc_info())+'\n')
    return #sys.exit(0) # just return doesn't exit!
signal.signal(signal.SIGPIPE, signalPIPE_handler)

_old_excepthook = sys.excepthook
def myexcepthook(exctype, value, intraceback):
  import sys
  import traceback
  sys.stderr.write("myexcepthook\n")
  if exctype == IOError:
    sys.stderr.write(" IOError intraceback:\n")
    traceback.print_tb(intraceback)
  else:
    _old_excepthook(exctype, value, intraceback)
sys.excepthook = myexcepthook

def _trace(frame, event, arg):
  if event == 'exception':
    while frame is not None:
      filename, lineno = frame.f_code.co_filename, frame.f_lineno
      sys.stderr.write("_trace exc frame: " + filename \
        + " " + str(lineno) + " " + str(frame.f_trace) + str(arg) + "\n")
      if arg[0] == IOError:
        myexcepthook(arg[0], arg[1], arg[2])
      frame = frame.f_back
  return _trace
sys.settrace(_trace)

def exiter():
  import sys
  sys.stderr.write("Exiting\n")
atexit.register(exiter)

def main():
  teststr = "Hello " * 5
  try:
    sys.stdout.write(teststr + "\n")
    sys.stdout.flush()
  except IOError:
    sys.stderr.write("Exc: " + str(sys.exc_info()[0]) + "\n")
    #sys.exit(0)


if __name__ == "__main__":
  main()

このスクリプトの実行方法の違いに注意してください。

$ python2.7 testprint.py | echo

signalPIPE_handler!(None, None, None)
_trace exc frame: testprint.py 44 <function _trace at 0xb748e5dc>(<type 'exceptions.IOError'>, (32, 'Broken pipe'), <traceback object at 0xb748acac>)
myexcepthook
 IOError intraceback:
  File "testprint.py", line 44, in main
    sys.stdout.flush()
_trace exc frame: testprint.py 51 None(<type 'exceptions.IOError'>, (32, 'Broken pipe'), <traceback object at 0xb748acac>)
myexcepthook
 IOError intraceback:
  File "testprint.py", line 44, in main
    sys.stdout.flush()
Exc: <type 'exceptions.IOError'>
Exiting

$ python3.2 testprint.py | echo

signalPIPE_handler!(None, None, None)
_trace exc frame: testprint.py 44 <function _trace at 0xb74247ac>(<class 'IOError'>, (32, 'Broken pipe'), <traceback object at 0xb747393c>)
myexcepthook
 IOError intraceback:
  File "testprint.py", line 44, in main
    sys.stdout.flush()
_trace exc frame: testprint.py 51 None(<class 'IOError'>, (32, 'Broken pipe'), <traceback object at 0xb747393c>)
myexcepthook
 IOError intraceback:
  File "testprint.py", line 44, in main
    sys.stdout.flush()
Exc: <class 'IOError'>
signalPIPE_handler!(None, None, None)
Exiting
signalPIPE_handler!(None, None, None)
Exception IOError: (32, 'Broken pipe') in <_io.TextIOWrapper name='<stdout>' mode='w' encoding='UTF-8'> ignored

signalPIPE_handlerPython 3 では 2 倍以上実行されることに注意してください! Pythonに何らかの「例外キュー」があれば、それを「のぞき見」して、メッセージsignalPIPE_handlerを抑制するために残りのイベントを削除できるException ... ignoredと思います...しかし、私はそのようなことを知りません.

最後に、これらのリソースは、デバッグを試みるときに便利ですgdb:

...私は を持っていないのでpython3-dbg、これはすべて機械語命令 ( layout asmin gdb、次に Ctrl-X + A) をステップ実行することになりますが、これではあまりわかりません。しかし、ここで問題を引き起こす方法は次のgdbとおりです。

1 つの端末で:

$ mkfifo foo 
$ gdb python3.2
...
Reading symbols from /usr/bin/python3.2...(no debugging symbols found)...done.
(gdb) run testprint.py > foo
Starting program: /usr/bin/python3.2 testprint.py > foo

ここでブロックします。同じディレクトリの別の端末で次のようにします。

$ echo <foo

...その後、最初のターミナルに戻ります。次のように表示されます。

...
Starting program: /usr/bin/python3.2 testprint.py > foo
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".

Program received signal SIGPIPE, Broken pipe.
0x0012e416 in __kernel_vsyscall ()
(gdb) bt
#0  0x0012e416 in __kernel_vsyscall ()
#1  0x0013c483 in __write_nocancel () from /lib/i386-linux-gnu/libpthread.so.0
#2  0x0815b549 in ?? ()
#3  0x08170507 in ?? ()
#4  0x08175e43 in PyObject_CallMethodObjArgs ()
#5  0x0815df21 in ?? ()
#6  0x0815f94e in ?? ()
#7  0x0815fb05 in ?? ()
#8  0x08170507 in ?? ()
#9  0x08175cb1 in _PyObject_CallMethod_SizeT ()
#10 0x08164851 in ?? ()
#11 0x080a3a36 in PyEval_EvalFrameEx ()
#12 0x080a3a53 in PyEval_EvalFrameEx ()
#13 0x080a43c8 in PyEval_EvalCodeEx ()
#14 0x080a466f in PyEval_EvalCode ()
#15 0x080c6e9d in PyRun_FileExFlags ()
#16 0x080c70c0 in PyRun_SimpleFileExFlags ()
#17 0x080db537 in Py_Main ()
#18 0x0805deee in main ()
(gdb) finish
Run till exit from #0  0x0012e416 in __kernel_vsyscall ()
0x0013c483 in __write_nocancel () from /lib/i386-linux-gnu/libpthread.so.0
...

残念ながら、Python3 をソースからビルドしてデバッグすることはできません。なので詳しい方の回答をお待ちしております:)

乾杯!

于 2013-05-01T09:37:39.923 に答える
3

このエラー メッセージは Python であり、提供されたパイプライン定義が壊れていることを示しています

echo は実際には stdin を介した入力を受け入れないため、Python からの入力パイプは早期に閉じられてしまいます。表示されている追加の例外 (例外ハンドラーの外側) は、インタープリターがシャットダウンしているときに標準ストリームを暗黙的にフラッシュしようとしたためです。これは、ユーザーが提供する Python コードの範囲外で発生するため、インタープリターはstderr通常の例外処理を呼び出すのではなく、単にエラーを書き込みます。

ユースケースで壊れたパイプを気にしないことがわかっている場合stdoutは、プログラムの終了前に明示的に閉じることでこのケースに対処できます。壊れたパイプについてはまだ文句を言いますが、通常どおり例外をキャッチして抑制できるようにします。

import sys

def main():
  teststr = "Hello " * 5
  try:
    sys.stdout.write(teststr + "\n")
    sys.stdout.flush()
  except IOError:
    sys.stderr.write("Exc: " + str(sys.exc_info()[0]) + "\n")
  try:
    sys.stdout.close()
  except IOError:
    sys.stderr.write("Exc on close: " + str(sys.exc_info()[0]) + "\n")

if __name__ == "__main__":
  main()

このバージョンでは、予想される出力のみが表示されます。これは、インタープリターのシャットダウン中に、ストリームを閉じようとしても、ストリームが既に閉じているとマークされていることを確認するのに十分であるためです。

$ python3 testprint.py | echo

Exc: <class 'BrokenPipeError'>
Exc on close: <class 'BrokenPipeError'>
于 2013-09-23T08:07:43.710 に答える