15

Python 中有一个已知问题,当 stdout 上发生“Broken pipe”时,“close failed in file object destructor” - Python tracker Issue 11380;在 python 中也可以看到- 为什么我的 Python3 脚本在将其输出传送到头部或尾部(sys 模块)时犹豫不决?- 堆栈溢出

我想要做的是在 Python 2.7 和 Python 3+ 发生此问题时打印出相同的自定义消息。所以我准备了一个测试脚本,testprint.py并运行它(在bashUbuntu 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 pipeline 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

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

...正如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 IOErrorPython 3 还不够“”吗?但它必须是——否则它不会打印自定义的“ Exc:...”消息!

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

4

3 回答 3

4

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

问题 6294:改进关闭异常忽略消息 - Python 跟踪器

此错误消息是在 PyErr_WriteUnraisable 中生成的,它从许多上下文中调用,包括 __del__ 方法。关闭期间调用的 __del__ 方法很可能是产生您所说的错误的原因,但据我所知, __del__ 方法无法知道它是在关闭期间被调用的。因此,对消息的建议修复将不起作用。[....]
但是,因为这是一条您甚至无法捕获的消息,所以更改它应该是完全安全的。

嗯,谢谢你不能trap的这个消息,很方便。我相信这在某种程度上与在del () - Stack Overflow中打印到 stderr 的忽略异常有关,尽管该帖子(显然)谈到了自定义__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,所有这一切都简化为逐步执行机器指令(layout asm在 中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 并对其进行调试;所以我希望知道的人回答:)

干杯!

于 2013-05-01T09:37:39.923 回答
3

此错误消息是 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'>
于 2013-09-23T08:07:43.710 回答
1

This is a VERY ugly hack to suppress the error message from being shown in case printing to stdout caused a broken pipe (e.g. because a pager process invoked like your-program.py | less was quit without scrolling to the bottom of the output:

try:
    actual_code()
except BrokenPipeError:
    sys.stdout = os.fdopen(1)
于 2015-12-15T21:03:56.430 回答