2012-10-08 83 views
39

注意:我没有试图重现下面在Windows下面描述的问题,或者使用2.7.3以外的Python版本。如何沉默“sys.excepthook失踪”错误?

最可靠的方法来引起讨论的问题是管下面的测试脚本通过:输出(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 

回答

60

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的输出对象不存在,Python代码也会包含一个应急fprintf以直接向文件指针输出一些输出。

技术细节

对于那些有兴趣在此过程中的细节,让我们来看看Python解释器的关闭序列,这是在pythonrun.cPy_Finalize function实现。

  1. 在调用退出钩子并关闭线程后,终止代码调用PyImport_Cleanup来完成和取消分配所有导入的模块。通过此功能进行的下一个到最后一个任务是removing the sys module,主要由呼叫_PyModule_Clear以清除该模块的字典中的所有条目 - 包括,尤其是标准流对象(Python的对象),如stdoutstderr
  2. 当一个值从字典移除或使用the Py_DECREF macro由新值代替,its reference count is decremented。引用计数达到零的对象有资格取消分配。由于sys模块持有对标准流对象的最后剩余引用,因此当这些引用未被设置为_PyModule_Clear时,它们随时可以被解除分配。
  3. 释放Python文件对象是由the file_dealloc functionfileobject.c中完成的。该第一invokes the Python file object's close method使用恰当地命名close_the_file function

    ret = close_the_file(f); 
    

    对于一个标准的文件对象,close_the_file(f)delegates to the C fclose function,如果仍然存在要被写入到文件指针数据其设定一个错误条件。 file_dealloc然后检查是否存在错误情况,并打印你看到的第一个消息:

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

    hook = PySys_GetObject("excepthook"); 
    

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

    if (hook && hook != Py_None) { 
        ... 
    } else { 
        PySys_WriteStderr("sys.excepthook is missing\n"); 
        PyErr_Display(exception, v, tb); 
    } 
    
  5. 通知我们缺少excepthook后,然后Python回落到打印使用PyErr_Display异常信息,这是用于显示堆栈跟踪的默认方法。这个功能的第一件事是尝试访问sys.stderr

    PyObject *f = PySys_GetObject("stderr"); 
    

    在这种情况下,这并不因为sys.stderr工作已经清零,无法访问。 因此,代码直接调用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,直到_PyImport_Fini运行时才会释放,later in the implementation of Py_Finalize这是,当最后一次对标准流对象的引用消失时才会释放。一旦引用计数达到零,Py_DECREF立即释放对象。但是,主要答案的所有内容都是从sys模块的字典中删除引用,并在稍后的某个时间释放。

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

除非您知道前面脚注中提到的属性缓存机制,否则这一行为是唯一不合理的部分。

+0

一个非常清晰和简洁的解释,通常会让我抓住氧气罐(溺水,需要更多,空气!),谢谢! –

+0

在其他程序需要python脚本的输出的情况下,例如, grep,有没有比python printer.py更好的实现方式grep“abc”'? –

+0

@MarkZ。诚实地说,最好的解决方案不是将Python脚本的输出传递给不读取它的程序 - 即避免造成这个问题首先出现的整个情况。如果由于某种奇怪的原因而无法实现,可以使用'--quiet'或'--silent'等命令行选项来抑制Python脚本的所有输出。 –

1

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

你***尝试* **这个解决方案和我提供的测试脚本?毕竟,这就是我提供它的原因...... – kjo

10

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

然后使用此脚本没有任何反应,作为异常(IO错误:[错误32]破碎的管)由尝试...除了抑制。

$ python testscript.py | : 
$ 
-3

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

print "Good Bye" 

溶液的语法简单地固定:

print ("Good Bye") 

[树莓派零,Python的2.7.9]

+1

在Python 2.7.9中,在'print'中有括号并没有区别。 – DyZ