在Python 3中禁止打印输出“Exception ... ignored”消息

时间:2013-05-01 07:27:13

标签: python bash python-2.7 python-3.x

Python中存在一个已知问题,其中"close failed in file object destructor" when "Broken pipe" happens on stdout - Python tracker Issue 11380;也见于python - Why does my Python3 script balk at piping its output to head or tail (sys module)? - Stack Overflow

我想要做的是,在Python 2.7和Python 3+中发生此问题时打印出相同的自定义消息。所以我准备了一个测试脚本testprint.py并运行它(在bash,Ubuntu 11.04中显示的片段):

$ 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

正如以上链接所预期的那样,有两种不同的消息。在Help with a piping 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

好的,越来越近了......现在,“忽略”这些异常(或者在我的情况下,用自定义错误消息替换)的方法是处理它们:

Ignore exceptions - comp.lang.python

  

&GT;有没有办法让[翻译]忽略例外   不。处理异常或编写没有异常的代码   产生例外。

...和An Introduction to Python - Handling Exceptions注意到,这样做的方法是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

好的,所以尝试/除了我希望它用于Python 2.7之外 - 但是,然后,Python 3.2都按预期处理,仍然生成Exception ... ignored消息!问题是什么 - 对于Python 3来说不是“except IOError”吗?但它必须 - 否则它不会打印自定义“Exc:...”消息!

那么 - 这里有什么问题,为什么Exception ... ignored仍然在Python 3中打印,即使我正在处理异常?更重要的是,我如何处理它,以便Exception ... ignored 再打印出来?

3 个答案:

答案 0 :(得分:3)

关于这一点的更多说明 - 问题仍未解决......首先:

Issue 6294: Improve shutdown exception ignored message - Python tracker

  

此错误消息在PyErr_WriteUnraisable中生成,即   从许多上下文中调用,包括__del__方法。一个__del__方法   在关机期间调用很可能是产生错误的原因   正在谈论,但据我所知__del__方法没有办法   知道特别是在关机期间被调用。所以   建议修复消息将无法正常工作。 [....]
  但是,因为这是一条消息,你甚至无法捕获它   应该完全安全地改变它。

好吧,谢谢你这个消息,你不能陷阱,非常方便。我认为这与Ignore exceptions printed 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

请注意{3}在Python 3中运行的次数是{2}的两倍!我想,如果在Python中存在某种“异常队列”,我可以“窥视”它,并删除signalPIPE_handler中的剩余事件,以便抑制signalPIPE_handler消息......但我不知道有什么这样的事情。

最后,尝试使用Exception ... ignored进行调试时,这些资源很不错:

...因为我没有gdb,所有这一切都缩小为单步执行机器指令(python3-dbg中的layout asm,然后是Ctrl-X + A),这不是'真的告诉我很多。但是这里是如何在gdb中触发问题:

在一个终端:

gdb

这里会阻止;在同一指令的另一个终端中:

$ 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

不幸的是,我没有可能从源代码构建Python3并立即调试它;所以我希望得到知道... 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 ...

的人的回答

干杯!

答案 1 :(得分:3)

此错误消息是Python,表明提供的管道定义已损坏,尽管有些混乱(请参阅http://bugs.python.org/issue11380

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'>

答案 2 :(得分:0)

这是一个非常丑陋的黑客,以防止在打印到stdout导致管道损坏的情况下显示错误消息(例如,因为调用了像$location.path(‘/‘)这样的寻呼机进程而没有滚动到输出的底部:

your-program.py | less