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

kjo*_*kjo 46 python io exception

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

引出问题的最可靠方法是通过:(under bash)管道以下测试脚本的输出:

try:
    for n in range(20):
        print n
except:
    pass
Run Code Online (Sandbox Code Playgroud)

即:

% python testscript.py | :
close failed in file object destructor:
sys.excepthook is missing
lost sys.stderr
Run Code Online (Sandbox Code Playgroud)

我的问题是:

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

(正如测试脚本所示,错误不能用a来捕获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
Run Code Online (Sandbox Code Playgroud)

Dav*_*d Z 67

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

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

发生这种情况的原因是,您正在从Python脚本中输出非零数量的输出到从不从标准输入读取的内容.这不是:命令所特有的; 您可以通过管道连接到任何不读取标准输入的命令来获得相同的结果,例如

python testscript.py | cd .
Run Code Online (Sandbox Code Playgroud)

或者,对于一个更简单的示例,请考虑一个仅printer.py包含的脚本

print 'abcde'
Run Code Online (Sandbox Code Playgroud)

然后

python printer.py | python printer.py
Run Code Online (Sandbox Code Playgroud)

会产生同样的错误.

当您将一个程序的输出传递给另一个程序时,写入程序生成的输出将备份到缓冲区中,并等待读取程序从缓冲区请求该数据.只要缓冲区是非空的,任何关闭写入文件对象的尝试都应该失败并出现错误.这是您看到的消息的根本原因.

触发错误的特定代码在Python的C语言实现中,这解释了为什么你不能用try/ exceptblock 捕获它:它在脚本内容完成处理后运行.基本上,当Python正在关闭时,它会尝试关闭stdout,但这会失败,因为仍有缓冲输出等待读取.因此,Python会尝试按正常情况报告此错误,但sys.excepthook已在作为完成过程的一部分时将其删除,因此失败.然后Python尝试打印一条消息sys.stderr,但是这已经被解除分配,它失败了.您在屏幕上看到消息的原因是Python代码确实包含一个意外事件fprintf,即直接将一些输出写入文件指针,即使Python的输出对象不存在.

技术细节

对于那些对这个过程的细节感兴趣的人,让我们来看看Python解释器的关闭序列,它是在Py_Finalize函数中实现的pythonrun.c.

  1. 在调用退出挂钩并关闭线程之后,终结代码调用PyImport_Cleanup以最终确定并释放所有导入的模块.此函数执行的倒数第二个任务是删除sys模块,该模块主要包括调用_PyModule_Clear清除模块字典中的所有条目 - 特别是包括标准流对象(Python对象),如stdoutstderr.
  2. 当值是从字典移除或由一个新的值代替,其引用计数被递减使用Py_DECREF.引用计数达到零的对象有资格进行重新分配.由于sys模块保留了对标准流对象的最后剩余引用,因此当这些引用未设置时_PyModule_Clear,它们就可以被释放.1
  3. Python文件对象的重新分配由实现file_dealloc功能fileobject.c.这首先使用aptly-named 函数调用Python文件对象的close方法:close_the_file

    ret = close_the_file(f);
    
    Run Code Online (Sandbox Code Playgroud)

    对于标准文件对象,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);
    }
    
    Run Code Online (Sandbox Code Playgroud)
  4. 打印完该消息后,Python会尝试使用显示异常PyErr_Print.委托PyErr_PrintEx,并作为其功能的一部分,PyErr_PrintEx尝试从中访问Python异常打印机sys.excepthook.

    hook = PySys_GetObject("excepthook");
    
    Run Code Online (Sandbox Code Playgroud)

    如果在Python程序的正常过程中完成,这将没有问题,但在这种情况下,sys.excepthook已经被清除.2 Python检查此错误情况并将第二条消息打印为通知.

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

    PyObject *f = PySys_GetObject("stderr");
    
    Run Code Online (Sandbox Code Playgroud)

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

    if (f == NULL || f == Py_None)
        fprintf(stderr, "lost sys.stderr\n");
    
    Run Code Online (Sandbox Code Playgroud)

有趣的是,Python 3.4+中的行为略有不同,因为最终化过程现在在内置模块被清除之前显式刷新标准输出和错误流.这样,如果您有等待写入的数据,则会收到明确表示该条件的错误,而不是正常完成过程中的"意外"失败.另外,如果你跑

python printer.py | python printer.py
Run Code Online (Sandbox Code Playgroud)

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


1实际上,这是谎言.Python的导入机制缓存每个导入模块的字典中的副本,这是不释放,直到_PyImport_Fini运行,后来在执行Py_Finalize,并且那是什么时候的标准流对象的最后一个引用消失.一旦引用计数达到零,立即Py_DECREF释放对象.但是对于主要答案而言,重要的是从模块的字典中删除引用,然后在以后解除分配.sys

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

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

  • 老实说,在执行`generateOutput.py |时,人们应该如何避免这个错误 在第一个屏幕上减少并且放弃"减少"?不写入`sys.stdout`(或者不写完全输出)并没有太大的解决方法.这与"你可以通过不在Python中编写代码来避免这个错误"一样有用. (11认同)
  • 实际上,这是应该在Python解释器本身修复的东西:http://bugs.python.org/issue11380 (3认同)
  • @DavidZ最明显的地方就是如果输出被打到头,这是一个非常常见的用例,如果你想在重定向到文件之前检查输出. (2认同)

And*_*rew 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
Run Code Online (Sandbox Code Playgroud)

然后使用此脚本没有任何反应,因为异常(IOError:[Errno 32] Broken pipe)被try ... except抑制.

$ python testscript.py  | :
$
Run Code Online (Sandbox Code Playgroud)