【问题标题】:Suppressing printout of "Exception ... ignored" message in Python 3在 Python 3 中抑制打印输出“Exception ...被忽略”消息
【发布时间】:2013-04-25 05:24:05
【问题描述】:

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 中显示的 sn-ps,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

> 有什么方法可以让 [interpreter] 忽略异常。
没有。要么处理异常,要么编写不处理异常的代码 生成异常。

...正如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

好的,所以 try/except 在 Python 2.7 中可以正常工作 - 但是,Python 3.2 都按预期处理,并且仍然会生成 Exception ... ignored 消息!有什么问题 - “except IOError”对于 Python 3 来说还不够吗?但它必须是——否则它不会打印自定义的“Exc:...”消息!

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

【问题讨论】:

  • 它适用于我的 head、tail、less、more 和 unique 所以看起来在与 echo 的交互中存在一个实际的错误。 “忽略异常”部分实际上是在解释器关闭期间尝试再次刷新标准流时发生的。

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


【解决方案1】:

关于这个的更多注释 - 问题仍未解决...首先:

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

请注意,signalPIPE_handler 在 Python 3 中的运行次数增加了两倍!我认为,如果 Python 中有某种“异常队列”,我可以在其中“窥视”,并删除 signalPIPE_handler 中的剩余事件,从而抑制 Exception ... ignored 消息......但我没有我不知道任何这样的事情。

最后,这些资源在尝试使用gdb 进行调试时非常有用:

...因为我没有python3-dbg,所以这一切都简化为逐步执行机器指令(gdb 中的layout asm,然后是 Ctrl-X + A),这并不能告诉我太多。但是这里是如何触发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

...然后返回第一个终端 - 你应该看到:

...
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 并对其进行调试;所以我希望得到知道:)的人的回答

干杯!

【讨论】:

    【解决方案2】:

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

    echo 实际上并不接受通过标准输入的输入,因此来自 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'>
    

    【讨论】:

      【解决方案3】:

      这是一个非常丑陋的 hack,用于在打印到标准输出导致管道损坏的情况下禁止显示错误消息(例如,因为像 your-program.py | less 这样调用的寻呼机进程在没有滚动到输出底部的情况下退出:

      try:
          actual_code()
      except BrokenPipeError:
          sys.stdout = os.fdopen(1)
      

      【讨论】:

      • 谢谢,为什么这行得通?
      猜你喜欢
      • 2012-08-06
      • 1970-01-01
      • 2021-10-21
      • 2012-04-14
      • 2013-02-18
      • 2020-11-03
      • 1970-01-01
      • 2012-09-30
      • 1970-01-01
      相关资源
      最近更新 更多