如何消除“sys.excepthook is missing”错误?
Posted
技术标签:
【中文标题】如何消除“sys.excepthook is missing”错误?【英文标题】:How to silence "sys.excepthook is missing" error? 【发布时间】:2012-09-29 05:04:41 【问题描述】:注意:我没有尝试在 Windows 下或使用 2.7.3 以外的 Python 版本重现下面描述的问题。
引发问题的最可靠方法是将以下测试脚本的输出通过:
(在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
我的问题是:
如何修改上面的测试脚本以避免如图所示运行脚本时出现错误信息(在Unix/
bash
下)?
(如测试脚本所示,无法使用try-except
捕获错误。)
诚然,上面的例子是高度人为的,但是当我的脚本的输出通过一些第 3 方软件传输时,我会遇到同样的问题有时。
错误消息肯定是无害的,但它让最终用户感到不安,所以我想让它保持沉默。
编辑:以下脚本与上面的原始脚本的不同之处仅在于它重新定义了 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
【问题讨论】:
【参考方案1】:如何修改上面的测试脚本以避免如图所示运行脚本时出现错误信息(在Unix/
bash
下)?
您需要阻止脚本将任何内容写入标准输出。这意味着删除任何print
语句和sys.stdout.write
的任何使用,以及任何调用它们的代码。
发生这种情况的原因是您将 Python 脚本中的非零输出量传送到从不从标准输入中读取的内容。这不是:
命令独有的;您可以通过管道传递到任何不读取标准输入的命令来获得相同的结果,例如
python testscript.py | cd .
或者举个更简单的例子,考虑一个脚本printer.py
,只包含
print 'abcde'
然后
python printer.py | python printer.py
会产生同样的错误。
当您将一个程序的输出通过管道传输到另一个程序时,写入程序产生的输出会备份在缓冲区中,并等待读取程序从缓冲区请求该数据。只要缓冲区非空,任何关闭写入文件对象的尝试都应该失败并出现错误。这是您看到的消息的根本原因。
触发错误的具体代码在Python的C语言实现中,这就解释了为什么你不能用try
/except
块来捕捉它:它在你的脚本内容完成处理后运行.基本上,当 Python 自行关闭时,它会尝试关闭 stdout
,但这会失败,因为仍有缓冲输出等待读取。因此 Python 尝试像往常一样报告此错误,但 sys.excepthook
已作为终结过程的一部分被删除,因此失败。 Python 然后尝试向sys.stderr
打印一条消息,但该消息已被再次释放,因此失败了。您在屏幕上看到消息的原因是 Python 代码确实包含一个意外事件 fprintf
以直接将一些输出写入文件指针,即使 Python 的输出对象不存在。
技术细节
对这个过程的细节感兴趣的,我们来看看Python解释器的关闭序列,在pythonrun.c
的Py_Finalize
function中实现。
-
在调用退出钩子并关闭线程后,终结代码调用
PyImport_Cleanup
来终结和释放所有导入的模块。该函数执行的倒数第二个任务是removing the sys
module,主要包括调用_PyModule_Clear
来清除模块字典中的所有条目——尤其包括标准流对象(Python对象),例如stdout
和 stderr
。
当一个值从字典中删除或替换为一个新值时,its reference count is decremented 使用the Py_DECREF
macro。引用计数为零的对象有资格进行释放。由于sys
模块保存了对标准流对象的最后剩余引用,因此当_PyModule_Clear
取消设置这些引用时,它们就可以被释放了。1
Python 文件对象的释放由the file_dealloc
function 中的fileobject.c
完成。这第一个invokes the Python file object's close
method 使用了恰当命名的close_the_file
function:
ret = close_the_file(f);
对于标准文件对象,close_the_file(f)
delegates to the C fclose
function,如果仍有数据要写入文件指针,则会设置错误条件。 file_dealloc
然后检查该错误情况并打印您看到的第一条消息:
if (!ret)
PySys_WriteStderr("close failed in file object destructor:\n");
PyErr_Print();
else
Py_DECREF(ret);
打印该消息后,Python 会尝试使用PyErr_Print
显示异常。它委托给PyErr_PrintEx
,作为其功能的一部分,PyErr_PrintEx
尝试从sys.excepthook
访问 Python 异常打印机。
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);
在通知我们丢失了excepthook
之后,Python 然后回退到使用PyErr_Display
打印异常信息,这是显示堆栈跟踪的默认方法。这个函数做的第一件事就是尝试访问sys.stderr
。
PyObject *f = PySys_GetObject("stderr");
在这种情况下,这不起作用,因为sys.stderr
已经被清除并且无法访问。3 所以代码直接调用fprintf
将第三条消息发送到 C 标准错误流。
if (f == NULL || f == Py_None)
fprintf(stderr, "lost sys.stderr\n");
有趣的是,Python 3.4+ 中的行为略有不同,因为在清除内置模块之前,终结过程现在 explicitly flushes the standard output and error streams。这样,如果您有等待写入的数据,您会收到一个明确表示该条件的错误,而不是正常完成过程中的“意外”失败。另外,如果你运行
python printer.py | python printer.py
使用 Python 3.4(当然是在 print
语句上加上括号之后),您根本不会收到任何错误。我想 Python 的第二次调用可能出于某种原因消耗了标准输入,但这是一个完全不同的问题。
1其实那是个谎言。 Python 的导入机制caches a copy of each imported module's dictionary,直到_PyImport_Fini
运行,later in the implementation of Py_Finalize
才发布,那是当对标准流对象的最后引用消失时。一旦引用计数达到零,Py_DECREF
立即释放对象。但对于主要答案而言,重要的是引用已从 sys
模块的字典中删除,然后在稍后的某个时间释放。
2同样,这是因为 sys
模块的字典在真正释放任何内容之前已被完全清除,这要归功于属性缓存机制。您可以使用 -vv
选项运行 Python,以在收到有关关闭文件指针的错误消息之前查看所有模块的属性都未设置。
3除非您了解前面脚注中提到的属性缓存机制,否则此特定行为是唯一没有意义的部分。
【讨论】:
一个非常清晰和简洁的解释,通常会让我抓住氧气罐(溺水。需要。更多。空气!),谢谢! 老实说,当人们在第一个屏幕上执行generateOutput.py | less
并退出less
时,应该如何避免这个错误?不写信给sys.stdout
(或不完全写输出)并不是什么解决方法。这与“您可以通过不在 Python 中编写代码来避免此错误”一样有用。
真的,这是应该在 Python 解释器本身中修复的:bugs.python.org/issue11380
@DavidZ 出现这种情况的最明显的地方是如果输出被移到头部,这是一个非常常见的用例,如果您希望在重定向到文件之前检查输出。
另一种可能不好的方法来消除错误:sys.stderr.close(); sys.stdout.close()
在脚本的最后【参考方案2】:
我今天自己也遇到了这种问题,然后去寻找答案。我认为这里的一个简单解决方法是确保您首先刷新 stdio,因此 python 阻塞而不是在脚本关闭期间失败。例如:
--- 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
然后这个脚本什么都不会发生,因为异常(IOError:[Errno 32] Broken pipe)被try...except抑制了。
$ python testscript.py | :
$
【讨论】:
【参考方案3】:在你的程序中抛出了一个使用 try/except 块无法捕获的异常。要抓住他,请覆盖函数sys.excepthook
:
import sys
sys.excepthook = lambda *args: None
来自documentation:
sys.excepthook(类型、值、回溯)
当异常被引发但未被捕获时,解释器调用 sys.excepthook 带三个参数,异常类,异常 实例和回溯对象。在交互式会话中,这 发生在控制权返回到提示符之前;在 Python 中 程序这发生在程序退出之前。的处理 可以通过分配另一个来自定义此类***异常 sys.excepthook 的三参数函数。
示例:
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
【讨论】:
您是否使用我提供的测试脚本尝试这个解决方案?毕竟,这就是我提供它的原因......【参考方案4】:我意识到这是一个老问题,但我在 Google 搜索错误时发现了它。就我而言,这是一个编码错误。我最后的声明之一是:
print "Good Bye"
解决方案只是将语法固定为:
print ("Good Bye")
[树莓派零,Python 2.7.9]
【讨论】:
在 Python 2.7.9 中,print
中有括号和没有括号没有区别。以上是关于如何消除“sys.excepthook is missing”错误?的主要内容,如果未能解决你的问题,请参考以下文章