如何沉默“sys.excepthook is missing”错误?

时间:2012-10-08 22:25:27

标签: python exception io

注意:我没有尝试在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捕获。)

不可否认,上面的例子非常人为,但是当我的脚本输出通过某些第三方软件传输时,我遇到了同样的问题有时

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

编辑:以下脚本与上面的原始脚本不同之处仅在于它重新定义了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

4 个答案:

答案 0 :(得分:65)

  

如何修改上面的测试脚本,以避免在脚本运行时出现错误消息(在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的{​​{3}}中实现。< / p>

  1. 在调用退出挂钩并关闭线程后,终结代码调用Py_Finalize function来完成并释放所有导入的模块。此函数执行的倒数第二个任务是PyImport_Cleanup,主要包括调用removing the sys module以清除模块字典中的所有条目 - 特别是包括标准流对象(Python对象),例如stdoutstderr
  2. 当使用_PyModule_Clear从字典中删除值或使用新值替换its reference count is decremented时。引用计数达到零的对象有资格进行重新分配。由于sys模块保留了对标准流对象的最后剩余引用,因此当_PyModule_Clear取消设置这些引用时,它们就可以被释放了。 1
  3. 释放Python文件对象由fileobject.c中的the Py_DECREF macro完成。第一个the file_dealloc function使用恰当命名的invokes the Python file object's close method

    ret = close_the_file(f);
    

    对于标准文件对象close_the_file(f) close_the_file function,如果仍有数据要写入文件指针,则会设置错误条件。 file_dealloc然后检查该错误情况并打印您看到的第一条消息:

    if (!ret) {
        PySys_WriteStderr("close failed in file object destructor:\n");
        PyErr_Print();
    }
    else {
        Py_DECREF(ret);
    }
    
  4. 打印完该消息后,Python会尝试使用delegates to the C fclose function显示异常。该代表委托给PyErr_Print,作为其功能的一部分,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);
    }
    
  5. 在通知我们缺少excepthook之后,Python会回退到使用PyErr_PrintEx打印异常信息,这是显示堆栈跟踪的默认方法。这个函数的第一件事就是尝试访问sys.stderr

    PyObject *f = PySys_GetObject("stderr");
    

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

    if (f == NULL || f == Py_None)
        fprintf(stderr, "lost sys.stderr\n");
    
  6. 有趣的是,Python 3.4+中的行为略有不同,因为现在在内置模块之前的最终化过程PyErr_Display被清除。这样,如果您有等待写入的数据,则会收到明确表示该条件的错误,而不是偶然的&#34;意外&#34;正常的最终程序失败。另外,如果你运行

    python printer.py | python printer.py
    

    使用Python 3.4(当然在括号上加print语句后),你根本不会得到任何错误。我认为Python的第二次调用可能由于某种原因而消耗标准输入,但这是一个完全独立的问题。


    1 实际上,这是谎言。 Python的导入机制explicitly flushes the standard output and error streams,在caches a copy of each imported module's dictionary运行之前不会发布,_PyImport_Fini在最后一次引用时 标准流对象消失。一旦引用计数达到零,Py_DECREF立即释放对象 。但主要答案的重点是,引用将从sys模块的字典中删除,然后在以后解除分配。

    2 同样,这是因为在真正释放任何内容之前,sys模块的字典被完全清除,这要归功于属性缓存机制。您可以使用-vv选项运行Python,以便在收到有关关闭文件指针的错误消息之前查看所有模块的属性未设置。

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

答案 1 :(得分:11)

我今天遇到了这类问题,并一直在寻找答案。我认为这里的一个简单的解决方法是确保首先刷新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 ...抑制。

$ python testscript.py  | :
$

答案 2 :(得分:2)

在你的程序中抛出一个使用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

答案 3 :(得分:-4)

我意识到这是一个老问题,但我在Google搜索中发现了错误。在我的情况下,这是一个编码错误。我最后的陈述之一是:

print "Good Bye"

解决方案只是将语法修复为:

print ("Good Bye")

[Raspberry Pi Zero,Python 2.7.9]