简体   繁体   English

禁止在Python 3中打印“ Exception…忽略”消息

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

There is a known issue in Python, where "close failed in file object destructor" when "Broken pipe" happens on stdout - Python tracker Issue 11380 ; Python中存在一个已知问题,即在stdout上发生“管道破损”时,“在文件对象析构函数中关闭失败”-Python跟踪器Issue 11380 ; also seen in python - Why does my Python3 script balk at piping its output to head or tail (sys module)? python中也可以看到-为什么我的Python3脚本不愿意将其输出传递到头或尾(sys模块)? - Stack Overflow . -堆栈溢出

What I want to do, is printout the same custom message when this problem happens, in both Python 2.7 and Python 3+. 我想做的是在发生此问题时在Python 2.7和Python 3+中打印出相同的自定义消息。 So I prepare a test script, testprint.py and run it (snippets shown done in bash , Ubuntu 11.04): 因此,我准备了一个测试脚本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

As expected from above links, there are two different messages. 正如上面的链接所预期的,有两种不同的消息。 In Help with a piping error (velocityreviews.com) , it is recommended to use sys.stdout.flush() to force Python 2 to register an IOError instead of that message; 帮助中出现管道错误(velocityreviews.com)中 ,建议使用sys.stdout.flush()强制Python 2注册IOError而不是该消息。 with that, we have: 这样,我们有:

$ 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

OK, getting closer... Now, the way to "ignore" these exceptions (or in my case, replace with a custom error message), is to handle them: 好的,越来越近...现在,“忽略”这些异常的方法(在我的情况下,用自定义错误消息替换)是处理它们:

Ignore exceptions - comp.lang.python 忽略异常-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. 处理异常或编写不会生成异常的代码。

... and as An Introduction to Python - Handling Exceptions notes, the way to do that is a try/except block. ...并作为Python简介-处理异常说明,做到这一点的方法是try / except块。 So let's try that: 因此,我们尝试:

$ 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

Ok, so try/except works as I expect it for Python 2.7 - but then, Python 3.2 both handles as expected, and still generates a Exception ... ignored message! 好的,try / except可以按照我对Python 2.7的预期运行-但是,Python 3.2都可以按预期进行处理, 并且仍然会生成Exception ... ignored消息! What's the problem - isn't " except IOError " enough for Python 3? 这是什么问题-“ except IOError ”不足以支持Python 3吗? But it must be - otherwise it wouldn't have printed the custom " Exc:... " message! 但这必须是-否则就不会打印自定义的“ Exc:... ”消息!

So - what is the problem here, and why is the Exception ... ignored still printed in Python 3, even if I'm handling the exception? 所以-这里的问题是什么,为什么即使在处理异常的情况下,Python 3仍然会打印Exception ... ignored的异常? And more importantly, how do I handle it so the Exception ... ignored does not get printed anymore? 更重要的是,我该如何处理它,这样不会再显示Exception ... ignoredException ... ignored了?

Just some more notes about this - problem still not solved... first: 关于此的更多说明-问题仍然无法解决...首先:

Issue 6294: Improve shutdown exception ignored message - Python tracker 问题6294:改进了关闭异常忽略消息-Python跟踪器

This error message is generated in PyErr_WriteUnraisable, which is called from many contexts, including __del__ methods. 在PyErr_WriteUnraisable中生成此错误消息,该错误消息是在许多上下文(包括__del__方法)中调用的。 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. 在关闭过程中调用的__del__方法很可能会引起您正在谈论的错误,但据我所知,__del__方法无法知道在关闭过程中是否正在调用该方法。 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. 但是,由于这是一条消息,您甚至无法捕获它,因此更改它应该是完全安全的。

Well, thanks for this message that you cannot trap, very convenient. 好吧,非常感谢您收到的此消息,您无法捕获。 I believe this is somehow related to Ignore exceptions printed to stderr in del () - Stack Overflow , although that post (apparently) talks about custom __del__ methods. 我相信这与del ()-Stack Overflow中忽略打印到stderr的异常有关,尽管该文章(显然)谈到了自定义__del__方法。

Using a bit of the following resources: 使用以下一些资源:

... I modified the script, so I overload all possible handlers I can, to see if there isn't a space somewhere where I can "handle" this exception so it isn't "ignored": ...我修改了脚本,所以我尽可能地重载了所有可能的处理程序,以查看是否存在可以“处理”此异常的空间,以便不会“忽略”该异常:

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()

Note the difference in how this script runs: 注意此脚本的运行方式不同:

$ 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

Note that signalPIPE_handler runs two times more in Python 3! 请注意, signalPIPE_handler在Python 3中运行的次数增加了两倍! I think, if there was some sort of "exception queue" in Python, I could "peek" in it, and remove remaining events in the signalPIPE_handler , so as to suppress the Exception ... ignored message... but I don't know of any such thing. 我认为,如果Python中存在某种“异常队列”,我可以在其中“窥视”,并删除signalPIPE_handler中的剩余事件,以抑制Exception ... ignored消息...但是我不知道不知道任何这样的事情。

Finally, these resources are nice when trying to debug with gdb : 最后,在尝试使用gdb进行调试时,这些资源非常gdb

... since I don't have python3-dbg , all this reduces to stepping through machine instructions ( layout asm in gdb , then Ctrl-X + A), which doesn't really tell me much. ...因为我没有python3-dbg ,所以所有这些都减少了逐步执行机器指令(在gdb layout asm ,然后按Ctrl-X + A),这并不能告诉我太多。 But here is how to trigger the problem in gdb : 但是这里是如何触发gdb的问题:

In one terminal: 在一个终端中:

$ 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

Here it will block; 它会在这里阻塞; in another terminal in the same diretory do: 在同一目录的另一个终端中执行以下操作:

$ echo <foo

... then return to first terminal - you should see: ...然后返回第一个终端-您应该看到:

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

Unfortunately, I don't have the possibility to build Python3 from source and debug it now; 不幸的是,我无法从源代码构建Python3并对其进行调试。 so I'll hope for an answer from someone who knows :) 所以我希望从认识的人那里得到答案:)

Cheers! 干杯!

This error message is Python indicating that the supplied pipeline definition is broken, albeit in a somewhat confusing way (see http://bugs.python.org/issue11380 ) 此错误消息是Python,表示所提供的管道定义已损坏,尽管有些混乱(请参阅http://bugs.python.org/issue11380

echo doesn't actually accept input via stdin, so the input pipe from Python ends up being closed early. echo实际上并不接受通过stdin进行的输入,因此来自Python的输入管道最终会提前关闭。 The extra exception you're seeing (outside the exception handler) is then due to the implicit attempt to flush the standard streams as the interpreter is shutting down. 您看到的额外异常(异常处理程序之外)是由于在解释器关闭时隐式尝试刷新标准流。 This happens outside the scope of any user provided Python code, so the interpreter just writes the error to stderr rather than invoking normal exception processing. 这发生在任何用户提供的Python代码范围之外,因此解释器仅将错误写入stderr而不是调用常规异常处理。

If you know you don't care about broken pipes for your use case, you can deal with this case by explicitly closing stdout before the end of your program. 如果您知道自己不关心用例的管道中断,可以通过在程序结束之前显式关闭stdout来处理这种情况。 It will still complain about the broken pipe, but it will do it in a way that lets you catch and suppress the exception as usual: 它仍然会抱怨管道损坏,但是会像往常一样捕获并抑制异常:

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()

In this version, only the expected output is seen, because even the attempt at closing it is enough to ensure the stream is already marked as closed during interpreter shutdown: 在此版本中,仅看到预期的输出,因为即使尝试关闭它也足以确保在解释器关闭期间流已被标记为已关闭:

$ python3 testprint.py | echo

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

This is a VERY ugly hack to suppress the error message from being shown in case printing to stdout caused a broken pipe (eg because a pager process invoked like your-program.py | less was quit without scrolling to the bottom of the output: 这是一个非常丑陋的技巧,可以防止在打印到stdout导致管道损坏的情况下显示错误消息(例如,因为像your-program.py | less这样调用的寻呼机进程被退出而没有滚动到输出的底部:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM