在 Python 3 中抑制打印输出“异常...忽略”消息

2024-01-31

Python 中有一个已知问题,其中当 stdout 上发生“管道损坏”时,“文件对象析构函数中的关闭失败” - Python 跟踪器问题 11380 http://bugs.python.org/issue11380,#msg153320;也见于python - 为什么我的 Python3 脚本不愿意将其输出传输到 head 或 tail (sys 模块)? - 堆栈溢出 https://stackoverflow.com/questions/11423225/why-does-my-python3-script-balk-at-piping-its-output-to-head-or-tail-sys-module.

我想做的是,当这个问题发生时,在 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

正如上面链接所预期的那样,有两条不同的消息。在帮助解决管道错误 (velocityreviews.com) http://www.velocityreviews.com/forums/t749747-help-with-a-piping-error.html,建议使用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 http://compgroups.net/comp.lang.python/ignore-exceptions/1670676

> 有没有办法让[解释器]忽略异常。
没有。要么处理异常,要么编写不处理异常的代码 产生异常。

... 并作为Python 简介 - 处理异常 http://www.network-theory.co.uk/docs/pytut/HandlingExceptions.html请注意,执行此操作的方法是 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 does not不再打印吗?


关于此问题还有一些注意事项 - 问题仍未解决......首先:

问题 6294:改进关闭异常忽略消息 - Python 跟踪器 http://bugs.python.org/issue6294#msg89442

这个错误信息是在PyErr_WriteUnraisable中生成的,即 从许多上下文中调用,包括 __del__ 方法。 __del__ 方法 在关闭期间调用很可能是产生错误的原因 正在谈论,但据我所知 __del__ 方法无法 特别是知道它是在关闭期间被调用的。所以 对该消息提出的修复不起作用。 [...]
然而,因为这是一条消息,所以你甚至无法捕获它 更改它应该是完全安全的。

嗯,谢谢你的这个消息,你不能陷入困境,非常方便。我相信这在某种程度上与忽略打印​​到 stderr 的异常del() - 堆栈溢出 https://stackoverflow.com/questions/13977970/ignore-exceptions-printed-to-stderr-in-del,尽管那篇文章(显然)谈论的是定制__del__方法。

使用以下一些资源:

  • 使用 Python 的 sys.settrace() 既有趣又有利可图 |回忆的:属于或关于回忆的 http://reminiscential.wordpress.com/2012/04/17/use-pythons-sys-settrace-for-fun-and-for-profit/
  • Python全局异常处理 - 代码日志 https://stackoverflow.com/questions/6598053/python-global-exception-handling

...我修改了脚本,所以我重载了所有可能的处理程序,以查看是否没有空间可以“处理”此异常,这样它就不会被“忽略”:

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:

  • c - gdb - 使用管道调试 - 代码日志 https://stackoverflow.com/questions/1456253/gdb-debugging-with-pipe
  • linux - 使用gdb在指定的可执行文件之外单步执行汇编代码会导致错误“无法找到当前函数的边界” - 堆栈内存溢出 https://stackoverflow.com/questions/2420813/using-gdb-to-single-step-assembly-code-outside-specified-executable-causes-error

...因为我没有python3-dbg,所有这些都简化为逐步执行机器指令(layout asm in gdb,然后 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 并对其进行调试;所以我希望得到了解的人的答复:)

Cheers!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在 Python 3 中抑制打印输出“异常...忽略”消息 的相关文章

随机推荐