简体   繁体   English

如何消除“ sys.excepthook丢失”错误?

[英]How to silence “sys.excepthook is missing” error?

NB: I have not attempted to reproduce the problem described below under Windows, or with versions of Python other than 2.7.3. 注意:我没有尝试在Windows或2.7.3以外的Python版本下重现下面描述的问题。

The most reliable way to elicit the problem in question is to pipe the output of the following test script through : (under bash ): 引发问题的最可靠方法是通过以下管道将以下测试脚本的输出传递给:bash下):

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

Ie: 即:

% 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 )? 如图所示(在Unix / bash )运行脚本时, 如何修改上面的测试脚本以避免错误消息?

(As the test script shows, the error cannot be trapped with a try-except .) (如测试脚本所示,不能用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. 编辑:以下脚本与上面的原始脚本的不同之处仅在于它重新定义了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

How can I modify the test script above to avoid the error message when the script is run as shown (under Unix/ bash )? 如图所示(在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. 这意味着删除所有print语句以及对sys.stdout.write任何使用,以及调用这些语句的任何代码。

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. 发生这种情况的原因是,您正在将Python脚本中非零数量的输出传递给从未从标准输入中读取的内容。 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 再举一个简单的例子,考虑一个脚本printer.py其中仅包含以下内容:

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. 触发错误的特定代码在Python的C语言实现中,这说明了为什么您不能使用try / except块来捕获它:它在脚本内容完成处理后运行。 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. 基本上,当Python关闭自身时,它会尝试关闭stdout ,但这失败了,因为仍有缓冲输出等待读取。 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尝试像往常一样报告此错误,但是sys.excepthook已作为完成过程的一部分被删除,因此失败。 Python then tries to print a message to sys.stderr , but that has already been deallocated so again, it fails. 然后,Python尝试将消息打印到sys.stderr ,但是该消息已经被重新分配,因此它失败了。 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. 您在屏幕上看到消息的原因是,即使Python的输出对象不存在,Python代码确实包含一个偶发性fprintf可以将一些输出直接写出到文件指针。

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 . 对于那些对此过程的细节感兴趣的人,让我们看一下Python解释器的关闭序列,该序列在Py_Finalize函数pythonrun.c

  1. After invoking exit hooks and shutting down threads, the finalization code calls PyImport_Cleanup to finalize and deallocate all imported modules. 调用出口挂钩并关闭线程后,完成代码将调用PyImport_Cleanup来完成并取消分配所有导入的模块。 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 . 此函数执行的倒数第二个任务是删除sys模块 ,该模块主要包括调用_PyModule_Clear清除模块字典中的所有条目-尤其是包括标准流对象(Python对象),例如stdoutstderr
  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 . 从字典中删除某个值或将替换为新值时, 使用Py_DECREF宏将 其引用计数递减 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. 由于sys模块保留了对标准流对象的最后剩余引用,因此当_PyModule_Clear未设置这些引用时,便可以将它们释放。 1 1个
  3. Deallocation of a Python file object is accomplished by the file_dealloc function in fileobject.c . Python文件对象的重新分配由完成file_dealloc功能fileobject.c This first invokes the Python file object's close method using the aptly-named close_the_file function : 首先使用恰当命名的close_the_file函数 调用Python文件对象的close方法

     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. 对于标准文件对象, close_the_file(f) 委派给C fclose函数 ,该函数在仍有数据要写入文件指针的情况下设置错误条件。 file_dealloc then checks for that error condition and prints the first message you see: 然后, file_dealloc检查该错误情况并打印您看到的第一条消息:

     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 . 打印该消息后,Python然后尝试使用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 . 这委托给PyErr_PrintEx ,并且作为其功能的一部分, PyErr_PrintEx尝试从sys.excepthook访问Python异常打印机。

     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. 如果在Python程序的正常过程中这样做会很好,但是在这种情况下, sys.excepthook已被清除。 2 Python checks for this error condition and prints the second message as a notification. 2 Python检查此错误情况,并输出第二条消息作为通知。

     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. 在通知我们有关缺少的excepthook ,Python然后回退到使用PyErr_Display打印异常信息, PyErr_Display是显示堆栈跟踪的默认方法。 The very first thing this function does is try to access sys.stderr . 此功能首先要做的是尝试访问sys.stderr

     PyObject *f = PySys_GetObject("stderr"); 

    In this case, that doesn't work because sys.stderr has already been cleared and is inaccessible. 在这种情况下,这将不起作用,因为sys.stderr已经被清除并且无法访问。 3 So the code invokes fprintf directly to send the third message to the C standard error stream. 3因此,代码直接调用fprintf将第三条消息发送到C标准错误流。

     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. 有趣的是,该行为在Python 3.4+中有所不同,因为完成过程现在在清除内置模块之前会显式刷新标准输出和错误流 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. 使用Python 3.4(当然在print语句上加上括号之后),您根本不会遇到任何错误。 I suppose the second invocation of Python may be consuming standard input for some reason, but that's a whole separate issue. 我想第二次调用Python可能出于某种原因消耗了标准输入,但这是一个完全独立的问题。


1 Actually, that's a lie. 1实际上,这是一个谎言。 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. Python的导入机制缓存每个导入模块的字典中的副本 ,这是不释放,直到_PyImport_Fini运行, 后来在执行Py_Finalize时候的标准流对象的最后一个引用消失。 Once the reference count reaches zero, Py_DECREF deallocates the objects immediately . 一旦引用计数达到零, Py_DECREF 就会立即释放对象。 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. 但是,对于主要答案而言,最重要的是将引用从sys模块的字典中删除,然后在以后的某个时间释放。

2 Again, this is because the sys module's dictionary is cleared completely before anything is really deallocated, thanks to the attribute caching mechanism. 2再次,这是由于属性缓存机制使sys模块的字典在真正释放之前完全清除了。 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. 您可以使用-vv选项运行Python,以查看所有模块的属性都未设置,然后获取有关关闭文件指针的错误消息。

3 This 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. 3除非您了解前面脚注中提到的属性缓存机制,否则这部分行为是唯一没有意义的部分。

I ran into this sort of issue myself today and went looking for an answer. 我今天自己遇到了这样的问题,然后去寻找答案。 I think a simple workaround here is to ensure you flush stdio first, so python blocks instead of failing during script shutdown. 我认为这里的一个简单解决方法是确保首先刷新stdio,因此python阻止而不是在脚本关闭期间失败。 For example: 例如:

--- 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

Then with this script nothing happens, as the exception (IOError: [Errno 32] Broken pipe) is suppressed by the try...except. 然后,使用此脚本没有任何反应,因为try ... except抑制了异常(IOError:[Errno 32]管道损坏)。

$ python testscript.py  | :
$

In your program throws an exception that can not be caught using try/except block. 在您的程序中引发无法使用try / except块捕获的异常。 To catch him, override function sys.excepthook : 要捕获他,请重写sys.excepthook函数:

import sys
sys.excepthook = lambda *args: None

From documentation : 文档

sys.excepthook(type, value, traceback) sys.excepthook(类型,值,回溯)

When an exception is raised and uncaught, the interpreter calls sys.excepthook with three arguments, the exception class, exception instance, and a traceback object. 引发并捕获异常时,解释器将使用三个参数,异常类,异常实例和回溯对象调用sys.excepthook。 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. 在Python程序中,这恰好在程序退出之前发生。 The handling of such top-level exceptions can be customized by assigning another three-argument function to sys.excepthook. 可以通过将另一个三参数函数分配给sys.excepthook来定制此类顶级异常的处理。

Illustrative example: 说明性示例:

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

I realize that this is an old question, but I found it in a Google search for the error. 我意识到这是一个古老的问题,但是我在Google搜索错误中找到了它。 In my case it was a coding error. 就我而言,这是一个编码错误。 One of my last statements was: 我最后的陈述之一是:

print "Good Bye"

The solution was simply fixing the syntax to: 解决方案只是将语法固定为:

print ("Good Bye")

[Raspberry Pi Zero, Python 2.7.9] [Raspberry Pi Zero,Python 2.7.9]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM