如何消除“sys.excepthook is missing”错误?

2024-04-20 12:30:29 发布

您现在位置:Python中文网/ 问答频道 /正文

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

我的问题是:

How can I modify the test script above to avoid the error message when the script is run as shown (under 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

Tags: thein脚本bashfor管道isstderr
3条回答

今天我自己也遇到了这样的问题,就去找答案。我认为这里的一个简单解决方法是确保首先刷新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]断开的管道)被try…except禁止。

$ python testscript.py  | :
$

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

import sys
sys.excepthook = lambda *args: None

来自documentation

sys.excepthook(type, value, traceback)

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. In an interactive session this happens just before control is returned to the prompt; in a Python program this happens just before the program exits. The handling of such top-level exceptions can be customized by assigning another three-argument function to 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

How can I modify the test script above to avoid the error message when the script is run as shown (under 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^{} function中实现的。

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

    ret = close_the_file(f);
    

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

    if (!ret) {
        PySys_WriteStderr("close failed in file object destructor:\n");
        PyErr_Print();
    }
    else {
        Py_DECREF(ret);
    }
    
  4. 打印该消息后,Python将尝试使用^{}显示异常。委托给^{},作为其功能的一部分,PyErr_PrintEx尝试从sys.excepthook访问Python异常打印机。

    hook = PySys_GetObject("excepthook");
    

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

    if (hook && hook != Py_None) {
        ...
    } else {
        PySys_WriteStderr("sys.excepthook is missing\n");
        PyErr_Display(exception, v, tb);
    }
    
  5. 在通知我们丢失的excepthook之后,Python会返回到使用^{}打印异常信息,这是显示堆栈跟踪的默认方法。这首赋的第一件事操作是尝试访问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的第二次调用可能出于某种原因正在使用标准输入,但这是一个完全独立的问题。


实际上,那是个谎言。Python的导入机制caches a copy of each imported module's dictionary,直到^{}运行,later in the implementation of ^{}才被释放,这是当最后一个对标准流对象的引用消失时。一旦引用计数为零,Py_DECREF立即释放对象。但对于主答案来说,所有重要的是从sys模块的字典中删除引用,然后稍后释放。

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

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

相关问题 更多 >