如何沉默“sys.excepthook失踪”错误?

注意:我没有试图重现下面在Windows下面描述的问题,或者使用2.7.3以外的Python版本。

引发问题的最可靠的方法是通过pipe道输出以下testing脚本(在bash下):

 try: for n in range(20): print n except: pass 

即:

 % python testscript.py | : close failed in file object destructor: sys.excepthook is missing lost sys.stderr 

我的问题是:

如何修改上面的testing脚本以避免脚本运行时出现错误信息(在Unix / bash )?

(如testing脚本所示,错误不能被try-except所困。)

上面的例子当然是非常人造的,但是当我的脚本输出通过一些第三方软件进行input时,我遇到了同样的问题。

错误信息当然是无害的,但对最终用户来说是令人不安的,所以我想要保持沉默。

编辑:下面的脚本,不同于上面的原来的只是因为它重新定义sys.excepthook,行为完全像上面给出的。

 import sys STDERR = sys.stderr def excepthook(*args): print >> STDERR, 'caught' print >> STDERR, args sys.excepthook = excepthook try: for n in range(20): print n except: pass 

如何修改上面的testing脚本以避免脚本运行时出现错误信息(在Unix / bash )?

您需要防止脚本将任何内容写入标准输出。 这意味着删除任何print语句和任何使用sys.stdout.write ,以及任何调用这些代码。

发生这种情况的原因是,你正在从你的Python脚本中输出一个非零值的输出到从未从标准input中读取的东西。 这不是唯一的:命令; 你可以通过pipe道input任何不读标准input的命令来得到相同的结果

 python testscript.py | cd . 

或者,对于一个更简单的例子,考虑一个脚本printer.py其中不包含任何内容

 print 'abcde' 

然后

 python printer.py | python printer.py 

会产生同样的错误。

当您将一个程序的输出传送到另一个程序时,写入程序产生的输出将被备份到缓冲区中,并等待读取程序从缓冲区中请求该数据。 只要缓冲区不是空的,任何closures写入文件对象的尝试都会失败并报错。 这是您看到的消息的根本原因。

触发错误的特定代码是在Python的C语言实现中,这就解释了为什么你不能用try / except块来捕捉它:它在脚本内容完成处理后运行。 基本上,当Pythonclosures时,它会尝试closuresstdout ,但是由于仍然有缓冲输出等待读取,所以失败了。 所以Python试图像往常一样报告这个错误,但是sys.excepthook已经作为最终化过程的一部分被删除了,所以失败了。 Python然后尝试打印一条消息到sys.stderr ,但是这已经被重新分配,它失败了。 在屏幕上看到消息的原因是,Python代码确实包含了一个意外事件fprintf ,即使Python的输出对象不存在,也可以直接向文件指针输出一些输出。

技术细节

对于那些对这个过程的细节感兴趣的人,我们来看一下Python解释器的closures序列,它在Py_Finalize函数中pythonrun.c

  1. 调用退出钩子并closures线程后,终止代码调用PyImport_Cleanup来完成和释放所有导入的模块。 该函数执行的倒数第二个任务是除去sys模块 ,该模块主要由调用_PyModule_Clear组成,以清除模块字典中的所有条目 – 特别包括标准stream对象(Python对象),如stdoutstderr
  2. 从字典中删除值或由新值replace时,使用Py_DECREFmacros 对其引用计数进行递减 。 引用计数达到零的对象有资格取消分配。 由于sys模块保存了最后剩下的对标准stream对象的引用,当这些引用被_PyModule_Clear取消设置时,它们就可以被释放了。 1
  3. 一个Python文件对象的释放是通过file_dealloc文件中的file_dealloc函数完成的。 这首先使用适当命名的close_the_file函数 调用Python文件对象的close方法 :

     ret = close_the_file(f); 

    对于一个标准的文件对象, close_the_file(f) 委托给C fclose函数 ,如果仍然有数据要写入文件指针,它将设置一个错误条件。 file_dealloc然后检查错误情况并打印你看到的第一条消息:

     if (!ret) { PySys_WriteStderr("close failed in file object destructor:\n"); PyErr_Print(); } else { Py_DECREF(ret); } 
  4. 打印PyErr_Print消息后,Python会尝试使用PyErr_Print显示exception。 PyErr_PrintEx ,作为其function的一部分, PyErr_PrintEx尝试从sys.excepthook访问Pythonexception打印机。

     hook = PySys_GetObject("excepthook"); 

    如果在Python程序的正常过程中完成,这样可以,但是在这种情况下, sys.excepthook已经被清除了。 2 Python检查这个错误情况,并打印第二条消息作为通知。

     if (hook && hook != Py_None) { ... } else { PySys_WriteStderr("sys.excepthook is missing\n"); PyErr_Display(exception, v, tb); } 
  5. 在通知我们关于丢失的excepthook ,Python又回到使用PyErr_Display打印exception信息,这是显示堆栈跟踪的默认方法。 这个函数做的第一件事是尝试访问sys.stderr

     PyObject *f = PySys_GetObject("stderr"); 

    在这种情况下,这不起作用,因为sys.stderr已经被清除并且不可访问。 3因此,代码直接调用fprintf将第三条消息发送到C标准错误stream。

     if (f == NULL || f == Py_None) fprintf(stderr, "lost sys.stderr\n"); 

有趣的是,Python 3.4+中的行为有点不同,因为在内置模块被清除之前,最终化过程现在明确地清除了标准输出和错误stream 。 这样,如果有数据等待写入,则会得到明确表示该情况的错误,而不是正常完成过程中的“意外”失败。 另外,如果你运行

 python printer.py | python printer.py 

使用Python 3.4(当然在print语句中加上括号之后),你根本不会得到任何错误。 我猜想Python的第二次调用可能由于某种原因需要使用标准input,但这是一个完全不同的问题。


其实,这是一个谎言。 Python的导入机制caching每个导入模块的字典的副本,直到_PyImport_Fini运行, 稍后在Py_Finalize的实现中才释放,这是最后一次对标准stream对象的引用消失的时候。 一旦引用计数达到零, Py_DECREF 立即释放对象。 但是,主要答案的所有重要的是,从sys模块的字典中删除引用,然后在一段时间后释放。

2再一次,这是因为sys模块的字典在任何事情真正释放之前都被清除了,这要归功于属性caching机制。 您可以使用-vv选项运行Python,以便在收到有关closures文件指针的错误消息之前查看所有未被设置的模块属性。

3除非您知道前面脚注中提到的属性caching机制,否则这个特定的行为是唯一没有意义的部分。

我今天自己遇到了这样的问题,一直在寻找答案。 我想这里的一个简单的解决方法是确保你先清理stdio,所以python块,而不是在脚本closures失败。 例如:

 --- a/testscript.py +++ b/testscript.py @@ -9,5 +9,6 @@ sys.excepthook = excepthook try: for n in range(20): print n + sys.stdout.flush() except: pass 

然后用这个脚本什么都不会发生,因为exception(IOError:[Errno 32] Broken pipe)被try …除外。

 $ python testscript.py | : $ 

在你的程序中抛出一个使用try / except块无法捕获的exception。 为了抓住他,重写函数sys.excepthook

 import sys sys.excepthook = lambda *args: None 

从文档 :

sys.excepthook(types,值,追溯)

当引发exception并且未被捕获时,解释器将调用带有三个参数的sys.excepthookexception类,exception实例和一个跟踪对象。 在交互式会话中,这恰好在控制返回提示之前发生; 在一个Python程序中,这个就在程序退出之前发生。 可以通过为sys.excepthook分配另一个三参数函数来自定义这些顶级exception的处理。

说明性的例子:

 import sys import logging def log_uncaught_exceptions(exception_type, exception, tb): logging.critical(''.join(traceback.format_tb(tb))) logging.critical('{0}: {1}'.format(exception_type, exception)) sys.excepthook = log_uncaught_exceptions 

我意识到这是一个古老的问题,但我发现它在谷歌search错误。 在我的情况下,这是一个编码错误。 我最后的一个陈述是:

 print "Good Bye" 

解决scheme是简单地修复语法:

 print ("Good Bye") 

[Raspberry Pi Zero,Python 2.7.9]