这是Debian Squeeze上的Python 2.6.6(默认)。请考虑以下Python代码。
import sys
try:
raise Exception("error in main")
pass
except:
exc_info = sys.exc_info()
finally:
try:
print "cleanup - always run"
raise Exception("error in cleanup")
except:
import traceback
print >> sys.stderr, "Error in cleanup"
traceback.print_exc()
if 'exc_info' in locals():
raise exc_info[0], exc_info[1], exc_info[2]
print "exited normally"
获得的错误是
Error in cleanup
Traceback (most recent call last):
File "<stdin>", line 10, in <module>
Exception: error in cleanup
cleanup - always run
Traceback (most recent call last):
File "<stdin>", line 3, in <module>
Exception: error in main
这个想法是为了应对某些代码或代码的清理(总是运行)或两者都给出错误的情况。例如,Ian Bicking在Re-raising Exceptions中对此进行了一些讨论。在该帖子的最后,(参见Update:
)他描述了如何处理代码+回滚/恢复的类似情况(仅在出现错误时运行)。
我摆弄了这个并提出了上面的代码,这有点像怪物。特别是,如果仅中有错误
清理(注释掉raise Exception("error in main")
),代码仍然正常退出,但它确实打印出一个追溯。目前,我给出了非清除错误优先级,因此它可以停止程序。
理想情况下,我想要阻止程序出错,但这似乎并不容易安排。 Python似乎只想提出一个错误,如果有的话会丢失其他错误,默认情况下它通常是最后一个错误。重新排列会产生如上所述的卷积。
使用locals()
也有点难看。一个人可以做得更好吗?
编辑:srgerg's answer向我介绍了上下文管理器和with
关键字的概念。除了PEP 343之外,我发现的其他相关文档(没有特别的顺序)。
Context Manager Types,The with statement和http://docs.python.org/reference/datamodel.html#context-managers。对于以前的方法来说,这似乎是一个很大的改进,即涉及trys,excepts和finallys的意大利面条代码。
总而言之,有两件事我想要这样的解决方案给我。
主要代码或中的异常能力 清理程序以阻止程序。上下文管理员这样做, 因为如果with循环的主体有一个例外和身体 退出没有,然后传播该异常。 如果退出会抛出异常并且with循环的主体没有, 然后传播。如果两者都抛出异常,那么退出 传播异常,而while循环体中的异常是 抑制。这些都记录在案,即来自 Context Manager Types,
contextmanager。退出(exc_type,exc_val,exc_tb)
退出运行时上下文并返回一个布尔标志,指示是否应该抑制发生的任何异常。 [...] 从此方法返回true值将导致with语句抑制异常并继续执行 声明后紧跟声明。否则,在此方法完成后,异常将继续传播 执行。执行此方法期间发生的异常将替换在with的正文中发生的任何异常 声明。 [...]传递的异常不应该明确地重新加注。相反,此方法应返回false值 表示该方法已成功完成,并且不希望抑制引发的异常。
如果这两个地方都有例外,我想看看追溯 来自两者,即使技术上只抛出一个例外。这是 true基于实验,因为如果两者都抛出异常, 然后传播退出异常,但是来自正文的回溯 仍然打印while循环,如 srgerg's answer。 但是,我无法找到这个记录 任何地方,都不能令人满意。
答案 0 :(得分:4)
理想情况下,你可以使用python with statement来处理try ... except
块内的清理,它看起来像这样:
class Something(object):
def __enter__(self):
print "Entering"
def __exit__(self, t, v, tr):
print "cleanup - always runs"
raise Exception("Exception occurred during __exit__")
try:
with Something() as something:
raise Exception("Exception occurred!")
except Exception, e:
print e
import traceback
traceback.print_exc(e)
print "Exited normally!"
当我运行它时,会打印:
Entering
cleanup - always runs
Exception occurred during __exit__
Traceback (most recent call last):
File "s3.py", line 11, in <module>
raise Exception("Exception occurred!")
File "s3.py", line 7, in __exit__
raise Exception("Exception occurred during __exit__")
Exception: Exception occurred during __exit__
Exited normally!
注意,任何一个异常都将终止该程序,并且可以在except
语句中处理。
修改:根据上面链接的with语句文档,__exit__()
方法只应在__exit__()
内发生错误时引发异常 - 也就是说,不应该重新提出传递给它的异常。
如果with
语句和__exit__()
方法中的代码都引发异常,则会出现此问题。在这种情况下,except子句中捕获的异常是__exit__()
中引发的异常。如果你想要在with语句中引发的那个,你可以这样做:
class Something(object):
def __enter__(self):
print "Entering"
def __exit__(self, t, v, tr):
print "cleanup - always runs"
try:
raise Exception("Exception occurred during __exit__")
except Exception, e:
if (t, v, tr) != (None, None, None):
# __exit__ called with an existing exception
return False
else:
# __exit__ called with NO existing exception
raise
try:
with Something() as something:
raise Exception("Exception occurred!")
pass
except Exception, e:
print e
traceback.print_exc(e)
raise
print "Exited normally!"
打印:
Entering
cleanup - always runs
Exception occurred!
Traceback (most recent call last):
File "s2.py", line 22, in <module>
raise Exception("Exception occurred!")
Exception: Exception occurred!
Traceback (most recent call last):
File "s2.py", line 22, in <module>
raise Exception("Exception occurred!")
Exception: Exception occurred!
答案 1 :(得分:1)
通过提供自定义exception hook:
,可以获得类似的行为import sys, traceback
def excepthook(*exc_info):
print "cleanup - always run"
raise Exception("error in cleanup")
traceback.print_exception(*exc_info)
sys.excepthook = excepthook
raise Exception("error in main")
示例输出:
cleanup - always run
Error in sys.excepthook:
Traceback (most recent call last):
File "test.py", line 5, in excepthook
raise Exception("error in cleanup")
Exception: error in cleanup
Original exception was:
Traceback (most recent call last):
File "test.py", line 9, in <module>
raise Exception("error in main")
Exception: error in main
在此示例中,代码的工作方式如下:
excepthook
。excepthook
运行一些清理代码(原始问题中的finally
下)。注意:我没有找到任何关于在钩子中出现问题时打印原始异常的文档,但我在cpython和jython中都看到过这种行为。特别是在cpython中我看到了以下实现:
void
PyErr_PrintEx(int set_sys_last_vars)
{
...
hook = PySys_GetObject("excepthook");
if (hook) {
...
if (result == NULL) {
...
PySys_WriteStderr("Error in sys.excepthook:\n");
PyErr_Display(exception2, v2, tb2);
PySys_WriteStderr("\nOriginal exception was:\n");
PyErr_Display(exception, v, tb);
...
}
}
}
答案 2 :(得分:0)
你非常接近一个简单的解决方案。只需在第一个异常中使用traceback.print_exc() - 然后您就不必再处理第二个异常了。这可能是这样的:
error7 = False
try:
raise Exception("error in main")
pass
except:
import traceback
traceback.print_exc()
error7 = True
finally:
print "cleanup - always run"
raise Exception("error in cleanup")
if error7:
raise SystemExit()
print "exited normally"
是否抛出异常的信息存储在error7
中,如果是这种情况,则在SystemExit()
块的末尾引发finally
。
启用了raise
个语句的输出:
cleanup - always run
Traceback (most recent call last):
File "G:/backed-up to mozy/Scripts/sandbox.py", line 3, in <module>
raise Exception("error in main")
Exception: error in main
Traceback (most recent call last):
File "<ipython-input-1-10089b43dd14>", line 1, in <module>
runfile('G:/backed-up to mozy/Scripts/sandbox.py', wdir='G:/backed-up to mozy/Scripts')
File "C:\Anaconda2\lib\site-packages\spyder\utils\site\sitecustomize.py", line 866, in runfile
execfile(filename, namespace)
File "C:\Anaconda2\lib\site-packages\spyder\utils\site\sitecustomize.py", line 87, in execfile
exec(compile(scripttext, filename, 'exec'), glob, loc)
File "G:/backed-up to mozy/Scripts/sandbox.py", line 11, in <module>
raise Exception("error in cleanup")
Exception: error in cleanup