Exception handling when errors may occur in main program or in cleanup
这是使用Python2.6.6(默认)的debian-squence。考虑下面的python代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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" |
得到的误差是
1 2 3 4 5 6 7 8 | 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)在《重新提出例外》一书中对此进行了一些讨论。在这篇文章的最后,(见
我摆弄了一下,想出了上面的代码,这有点怪。尤其是,如果清除(注释了
理想情况下,我希望两个错误中的一个停止程序,但这似乎不容易安排。python似乎只想引起一个错误,如果有的话会失去其他错误,默认情况下,它通常是最后一个错误。重新排列会产生像上面那样的卷积。
另外,使用
编辑:srgerg的回答向我介绍了上下文管理器和
总的来说,有两件事我希望这样的解决方案能给我。
在主代码或清除以使程序停止运行。上下文管理器这样做,因为如果with循环的主体有一个异常并且出口不存在,则会传播该异常。如果exit引发异常而with循环的主体没有,然后传播。如果两者都引发异常,则退出异常被传播,而while循环体中的异常被抑制的所有这些都记录在案,即上下文管理器类型,
contextmanager.exit(exc_type, exc_val, exc_tb)
Exit the runtime context and return a Boolean flag indicating if any exception that occurred should be suppressed. [...]
Returning a true value from this method will cause the with statement to suppress the exception and continue execution with the
statement immediately following the with statement. Otherwise the exception continues propagating after this method has finished
executing. Exceptions that occur during execution of this method will replace any exception that occurred in the body of the with
statement. [...] The exception passed in should never be reraised explicitly. instead, this method should return a false value to
indicate that the method completed successfully and does not want to suppress the raised exception.
如果这两个地方都有例外,我想看看回溯从这两个方面来看,即使技术上只抛出一个异常。这是基于实验,如果两者都抛出异常,然后传播退出异常,但从主体返回当循环仍在打印时,如斯格格的回答。但是,我找不到这个文件任何地方都不满意。
理想情况下,您可以使用python with语句来处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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!" |
当我运行这个时,它会打印:
1 2 3 4 5 6 7 8 9 10 | 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! |
注意,任何一个异常都将终止程序,并可以在
编辑:根据上面链接的WITH语句文档,
如果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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!" |
印刷品:
1 2 3 4 5 6 7 8 9 10 11 | 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 2 3 4 5 6 7 8 9 | 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") |
实例输出:
1 2 3 4 5 6 7 8 9 10 11 12 | 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中,我看到了以下实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | void PyErr_PrintEx(int set_sys_last_vars) { ... hook = PySys_GetObject("excepthook"); if (hook) { ... if (result == NULL) { ... PySys_WriteStderr("Error in sys.excepthook: "); PyErr_Display(exception2, v2, tb2); PySys_WriteStderr(" Original exception was: "); PyErr_Display(exception, v, tb); ... } } } |
你非常接近一个简单的解决方案。只需在第一个异常中使用traceback.print_exc(),就不必再处理第二个异常了。以下是可能出现的情况:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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" |
是否引发异常的信息存储在
启用两个
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | 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 |