关于bash:在Python 3中禁止打印输出“Exception … ignored”消息

Suppressing printout of “Exception … ignored” message in Python 3

在python中有一个已知的问题,即当stdout上发生"breakpipe"时,"close failed in file object destructor"(文件对象析构函数关闭失败)-python tracker问题11380;在python中也有类似问题-为什么我的python3脚本在将其输出管道输送到head或tail(sys模块)时犹豫不决?-堆栈溢出。

我想做的是,当这个问题发生时,在python 2.7和python 3+中打印出相同的自定义消息。因此,我准备了一个测试脚本,testprint.py并运行它(代码片段如bash中所示,Ubuntu11.04):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ cat > testprint.py <<"EOF"
import sys

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

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),建议使用sys.stdout.flush()强制python 2注册一个ioerror,而不是该消息;通过它,我们可以:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ cat > testprint.py <<"EOF"
import sys

def main():
  teststr ="Hello" * 5
  sys.stdout.write(teststr +"
"
)
  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

> Is there any way to make [interpreter] ignore exceptions.
Nope. Either handle the exceptions or write code that doesn't
generate exceptions.

…作为python处理异常说明的介绍,实现这一点的方法是一个try/except块。那么让我们试试看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
$ cat > testprint.py <<"EOF"
import sys

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

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

好的,所以试一下/except可以像我在python 2.7中预期的那样工作-但是,python 3.2都能按预期处理,并且仍然会生成一条Exception ... ignored消息!问题是什么?对于python3来说,"EDOCX1"(4)还不够吗?但一定是这样,否则它就不会打印"Exc:..."的自定义消息了!

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


此错误消息是python,指示所提供的管道定义已被破坏,尽管方式有些混乱(请参阅http://bugs.python.org/issue11380)。

echo实际上不接受通过stdin输入的内容,所以来自python的输入管道最终会提前关闭。您看到的额外异常(异常处理程序之外)是由于解释器关闭时隐式地尝试刷新标准流。这发生在任何用户提供的python代码范围之外,因此解释器只将错误写入stderr,而不调用正常的异常处理。

如果你知道你不关心你的用例的管道破裂,你可以在程序结束之前通过明确关闭stdout来处理这个案例。它仍然会抱怨管道破裂,但它会以一种让您像往常一样捕捉和抑制异常的方式进行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import sys

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

if __name__ =="__main__":
  main()

在此版本中,只看到预期的输出,因为即使尝试关闭它也足以确保在解释器关闭期间流已标记为关闭:

1
2
3
4
$ python3 testprint.py | echo

Exc: <class 'BrokenPipeError'>
Exc on close: <class 'BrokenPipeError'>

只是关于这个问题的一些笔记-问题还没有解决…第一:

问题6294:改进关闭异常忽略消息-python tracker

This error message is generated in PyErr_WriteUnraisable, which is
called from many contexts, including __del__ methods. A __del__ method
called during shutdown is most likely what is generating the error you
are speaking of, but as far as I know the __del__ method has no way to
know that it is being called during shutdown in particular. So the
proposed fix to the message won't work. [....]
However, because this is a message you can't even trap it
should be completely safe to change it.

好吧,谢谢你这条信息,你不能陷阱,非常方便。我相信这在某种程度上与忽略del()堆栈溢出中打印到stderr的异常有关,尽管这篇文章(显然)谈到了定制的__del__方法。

使用以下资源:

  • 使用python的sys.settrace()来获得乐趣和利润回忆:属于或关于回忆的
  • python全局异常处理-堆栈溢出

…我修改了脚本,因此我重载了所有可能的处理程序,以查看是否在某个地方没有可以"处理"此异常的空间,从而不被"忽略":

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import sys
import atexit
import signal
import inspect, pprint

def signalPIPE_handler(signal, frame):
    sys.stderr.write('signalPIPE_handler!'+str(sys.exc_info())+'
'
)
    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
"
)
  if exctype == IOError:
    sys.stderr.write(" IOError intraceback:
"
)
    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) +"
"
)
      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
"
)
atexit.register(exiter)

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


if __name__ =="__main__":
  main()

请注意此脚本运行方式的不同:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
$ 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

注意,在python 3中,signalPIPE_handler的运行次数是原来的两倍!我认为,如果在python中有某种"异常队列",我可以"窥视"其中的内容,删除signalPIPE_handler中的剩余事件,从而抑制Exception ... ignored消息……但我不知道有这样的事。

最后,在尝试使用gdb进行调试时,这些资源很好:

  • C-GDB-管道堆栈溢出调试
  • Linux-在指定的可执行文件外使用gdb单步汇编代码会导致错误"找不到当前函数的边界"-堆栈溢出

…由于我没有python3-dbg,所有这些都简化为单步执行机器指令(gdb中的layout asm,然后是ctrl-x+a),这并不能告诉我太多。但以下是如何引发gdb中的问题:

在一个终端中:

1
2
3
4
5
6
$ 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

在这里,它将阻塞;在同一个目录下的另一个终端中,它将阻塞:

1
$ echo <foo

…然后返回到第一个终端-您应该看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
...
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并对其进行调试;所以我希望知道:)的人能给出答案。

干杯!


如果打印到stdout导致管道断开(例如,因为像your-program.py | less这样调用的寻呼机进程在不滚动到输出底部的情况下退出),则禁止显示错误消息,这是一个非常难看的黑客:

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