如何沉默“sys.excepthook缺失”错误? [英] How to silence "sys.excepthook is missing" error?

查看:5956
本文介绍了如何沉默“sys.excepthook缺失”错误?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

注意:我没有尝试重现Windows下的下列问题,或者使用2.7.3以外的Python版本。



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

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

即:

 %python testscript.py | :
关闭失败的文件对象析构函数:
sys.excepthook缺少
丢失sys.stderr

我的问题是:


如何修改上述测试脚本当脚本运行时,如图所示(在Unix / bash )下运行脚本时,请避免出现错误消息?


(正如测试脚本所示,错误不能被尝试除外。)



上面的例子当然是非常人为的,但是当我的脚本的输出通过某些第三方软件管道时,我有时会遇到相同的问题。



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



编辑:以下脚本与原来的脚本不同之处仅在于它重新定义了sys.excepthook,其行为与上面给出的一样。

  import sys 
STDERR = sys.stderr
def excepthook(* args):
print>> STDERR,'catch'
print>> ($)

$ b sys.excepthook =
pass


解决方案


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


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



发生这种情况的原因是,您将Python脚本的非零输出管道从不从标准输入读取。这不是命令唯一的;您可以通过管道获得与读取标准输入的命令相同的结果,例如

  python testscript.py | cd。 

或者为了更简单的例子,考虑一个脚本 printer.py 只包含

  print'abcde'

然后

  python printer.py | python printer.py 

将产生相同的错误。



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



触发错误的具体代码是Python的C语言实现,这解释了为什么你可以'在尝试 / 块之外:在脚本的内容完成处理之后运行。基本上,当Python正在关闭时,它会尝试关闭 stdout ,但是失败是因为仍然有缓冲输出等待读取。所以Python试图通常报告这个错误,但是$ {code> sys.excepthook 已经在完成过程中被删除了,所以失败了。然后Python尝试将消息打印到 sys.stderr ,但是已经重新释放了这个消息,它失败了。您在屏幕上看到消息的原因是,Python代码确实包含一个偶然的 fprintf 直接向文件指针写出一些输出,即使Python的输出对象不是



技术细节



对于那些对此过程的细节感兴趣的人,让我们来看看Python解释器的关机顺序是在 Py_Finalize 函数 pythonrun.c


  1. 调用退出钩子并关闭线程后,最终化代码调用 PyImport_Cleanup 来完成和取消分配所有导入的模块。此功能执行的下一个最后一个任务是删除 sys 模块,其主要包括调用 _PyModule_Clear 清除模块字典中的所有条目,特别是标准流对象(Python对象),例如 stdout stderr

  2. 字典或替换为新值,其引用次数减少使用 Py_DECREF 。引用计数达到零的对象有资格获得解除分配。由于 sys 模块持有对标准流对象的最后剩余引用,当这些引用由 _PyModule_Clear 未设置时,它们然后准备被释放。 1

  3. Python文件对象的分配由 中的 file_dealloc 函数 fileobject.c 。第一个调用Python文件对象的关闭方法使用aptly命名的 close_the_file 功能

      ret = close_the_file(f ); 

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

      if(!ret){
    PySys_WriteStderr(在文件对象析构函数中关闭失败:\\\
    );
    PyErr_Print();
    }
    else {
    Py_DECREF(ret);
    }


  4. 打印该消息后,Python会尝试使用 PyErr_Print 。代表 PyErr_PrintEx ,作为其功能的一部分, PyErr_PrintEx 尝试从 sys.excepthook

      hook = PySys_GetObject(excepthook); 

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

      if(hook&& hook!= Py_None){
    ...
    } else {
    PySys_WriteStderr sys.excepthook is missing\\\
    );
    PyErr_Display(exception,v,tb);
    }


  5. 在通知我们关于缺少的 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\\\
    );


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

  python printer.py | python printer.py 

使用Python 3.4(将括号放在 print 语句当然),你根本没有任何错误。我想Python的第二次调用可能是因为某些原因而消耗标准输入,但这是一个完全不同的问题。






< sup> 1 其实这是谎言。 Python的导入机制缓存每个导入的模块的字典的副本 ,直到 _PyImport_Fini 运行,稍后执行的 Py_Finalize ,而 当标准流对象的最后一次引用消失时。一旦引用计数达到零, Py_DECREF 立即取消分配对象 。但是所有重要的主要答案是引用从 sys 模块的字典中删除,然后稍后释放。



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



3 这个特殊的行为是唯一没有意义的部分,除非你知道前面脚注中提到的属性缓存机制。


NB: I have not attempted to reproduce the problem described below under Windows, or with versions of Python other than 2.7.3.

The most reliable way to elicit the problem in question is to pipe the output of the following test script through : (under bash):

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

I.e.:

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

My question is:

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/bash)?

(As the test script shows, the error cannot be trapped with a try-except.)

The example above is, admittedly, highly artificial, but I'm running into the same problem sometimes when the output of a script of mine is piped through some 3rd party software.

The error message is certainly harmless, but it is disconcerting to end-users, so I would like to silence it.

EDIT: The following script, which differs from the original one above only in that it redefines sys.excepthook, behaves exactly like the one given above.

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

解决方案

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/bash)?

You will need to prevent the script from writing anything to standard output. That means removing any print statements and any use of sys.stdout.write, as well as any code that calls those.

The reason this is happening is that you're piping a nonzero amount of output from your Python script to something which never reads from standard input. This is not unique to the : command; you can get the same result by piping to any command which doesn't read standard input, such as

python testscript.py | cd .

Or for a simpler example, consider a script printer.py containing nothing more than

print 'abcde'

Then

python printer.py | python printer.py

will produce the same error.

When you pipe the output of one program into another, the output produced by the writing program gets backed up in a buffer, and waits for the reading program to request that data from the buffer. As long as the buffer is nonempty, any attempt to close the writing file object is supposed to fail with an error. This is the root cause of the messages you're seeing.

The specific code that triggers the error is in the C language implementation of Python, which explains why you can't catch it with a try/except block: it runs after the contents of your script has finished processing. Basically, while Python is shutting itself down, it attempts to close stdout, but that fails because there is still buffered output waiting to be read. So Python tries to report this error as it would normally, but sys.excepthook has already been removed as part of the finalization procedure, so that fails. Python then tries to print a message to sys.stderr, but that has already been deallocated so again, it fails. The reason you see the messages on the screen is that the Python code does contain a contingency fprintf to write out some output to the file pointer directly, even if Python's output object doesn't exist.

Technical details

For those interested in the details of this procedure, let's take a look at the Python interpreter's shutdown sequence, which is implemented in the Py_Finalize function of pythonrun.c.

  1. After invoking exit hooks and shutting down threads, the finalization code calls PyImport_Cleanup to finalize and deallocate all imported modules. The next-to-last task performed by this function is removing the sys module, which mainly consists of calling _PyModule_Clear to clear all the entries in the module's dictionary - including, in particular, the standard stream objects (the Python objects) such as stdout and stderr.
  2. When a value is removed from a dictionary or replaced by a new value, its reference count is decremented using the Py_DECREF macro. Objects whose reference count reaches zero become eligible for deallocation. Since the sys module holds the last remaining references to the standard stream objects, when those references are unset by _PyModule_Clear, they are then ready to be deallocated.1
  3. Deallocation of a Python file object is accomplished by the file_dealloc function in fileobject.c. This first invokes the Python file object's close method using the aptly-named close_the_file function:

    ret = close_the_file(f);
    

    For a standard file object, close_the_file(f) delegates to the C fclose function, which sets an error condition if there is still data to be written to the file pointer. file_dealloc then checks for that error condition and prints the first message you see:

    if (!ret) {
        PySys_WriteStderr("close failed in file object destructor:\n");
        PyErr_Print();
    }
    else {
        Py_DECREF(ret);
    }
    

  4. After printing that message, Python then attempts to display the exception using PyErr_Print. That delegates to PyErr_PrintEx, and as part of its functionality, PyErr_PrintEx attempts to access the Python exception printer from sys.excepthook.

    hook = PySys_GetObject("excepthook");
    

    This would be fine if done in the normal course of a Python program, but in this situation, sys.excepthook has already been cleared.2 Python checks for this error condition and prints the second message as a notification.

    if (hook && hook != Py_None) {
        ...
    } else {
        PySys_WriteStderr("sys.excepthook is missing\n");
        PyErr_Display(exception, v, tb);
    }
    

  5. After notifying us about the missing excepthook, Python then falls back to printing the exception info using PyErr_Display, which is the default method for displaying a stack trace. The very first thing this function does is try to access sys.stderr.

    PyObject *f = PySys_GetObject("stderr");
    

    In this case, that doesn't work because sys.stderr has already been cleared and is inaccessible.3 So the code invokes fprintf directly to send the third message to the C standard error stream.

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

Interestingly, the behavior is a little different in Python 3.4+ because the finalization procedure now explicitly flushes the standard output and error streams before builtin modules are cleared. This way, if you have data waiting to be written, you get an error that explicitly signals that condition, rather than an "accidental" failure in the normal finalization procedure. Also, if you run

python printer.py | python printer.py

using Python 3.4 (after putting parentheses on the print statement of course), you don't get any error at all. I suppose the second invocation of Python may be consuming standard input for some reason, but that's a whole separate issue.


1Actually, that's a lie. Python's import mechanism caches a copy of each imported module's dictionary, which is not released until _PyImport_Fini runs, later in the implementation of Py_Finalize, and that's when the last references to the standard stream objects disappear. Once the reference count reaches zero, Py_DECREF deallocates the objects immediately. But all that matters for the main answer is that the references are removed from the sys module's dictionary and then deallocated sometime later.

2Again, this is because the sys module's dictionary is cleared completely before anything is really deallocated, thanks to the attribute caching mechanism. You can run Python with the -vv option to see all the module's attributes being unset before you get the error message about closing the file pointer.

3This particular piece of behavior is the only part that doesn't make sense unless you know about the attribute caching mechanism mentioned in previous footnotes.

这篇关于如何沉默“sys.excepthook缺失”错误?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆