Previous error being masked by current exception context
以下是我在Doug Hellman网站上发现的一个名为"masking_exceptions_catch.py"的文件中的示例。 我暂时无法找到该链接。 抛出throws()中引发的异常,同时报告由cleanup()引发的异常。
在他的文章中,Doug评论说处理是非直观的。 中途期望它是Python版本中的一个bug或限制(大约在2009年),我在Mac的当前生产版本中运行它(2.7.6)。 它仍然报告cleanup()的异常。 我发现这有点惊人,并希望看到它是如何实际正确或理想的行为的描述。
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 27 28 29 30 31 | #!/usr/bin/env python import sys import traceback def throws(): raise RuntimeError('error from throws') def nested(): try: throws() except: try: cleanup() except: pass # ignore errors in cleanup raise # we want to re-raise the original error def cleanup(): raise RuntimeError('error from cleanup') def main(): try: nested() return 0 except Exception, err: traceback.print_exc() return 1 if __name__ == '__main__': sys.exit(main()) |
节目输出:
1 2 3 4 5 6 7 8 9 | $ python masking_exceptions_catch.py Traceback (most recent call last): File"masking_exceptions_catch.py", line 24, in main nested() File"masking_exceptions_catch.py", line 14, in nested cleanup() File"masking_exceptions_catch.py", line 20, in cleanup raise RuntimeError('error from cleanup') RuntimeError: error from cleanup |
盘旋回来回答。我首先回答你的问题。 :-)
这真的有用吗?
1 2 3 4 5 6 | def f(): try: raise Exception('bananas!') except: pass raise |
那么,上面做了什么? Cue Jeopardy音乐。
好吧,然后,铅笔下来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # python 3.3 4 except: 5 pass ----> 6 raise 7 RuntimeError: No active exception to reraise # python 2.7 1 def f(): 2 try: ----> 3 raise Exception('bananas!') 4 except: 5 pass Exception: bananas! |
嗯,那是富有成效的。为了好玩,让我们尝试命名异常。
1 2 3 4 5 6 | def f(): try: raise Exception('bananas!') except Exception as e: pass raise e |
现在怎么办?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | # python 3.3 4 except Exception as e: 5 pass ----> 6 raise e 7 UnboundLocalError: local variable 'e' referenced before assignment # python 2.7 4 except Exception as e: 5 pass ----> 6 raise e 7 Exception: bananas! |
异常语义在python 2和3之间发生了巨大的变化。但是如果python 2的行为在这里让你感到惊讶,那么考虑一下:它基本上与其他地方的python一致。
1 2 3 4 5 | try: 1/0 except Exception as e: x=4 #can I access `x` here after the exception block? How about `e`? |
所以,尴尬。例外是否应该特别限制在其封闭的词汇块中? Python 2说不,python 3说是。但我在这里过分简化了事情; bare
裸
常见用法是使用bare
直观地说,无参数
6.9。提高声明
raise_stmt ::= "raise" [expression ["," expression ["," expression]]] 如果没有表达式,则raise重新引发最后一个异常
在当前范围内活跃。BLOCKQUOTE>
所以,是的,这是python 2深层次的问题,与回溯信息的存储方式有关 - 在Highlander传统中,只能有一个(回溯对象保存到给定的堆栈帧)。因此,裸
raise 再次提出当前帧所认为的"最后"异常,这不一定是我们人类大脑认为的那个特定于我们所处的词汇嵌套异常块的异常。时间。呸,范围!那么,在python 3中修复了吗?
是。怎么样?新的字节码指令(两个,实际上,除了处理程序之外还有另一个隐含的指令),但真正关心的是 - 这一切都"直观地"起作用。您的示例代码不会获得
RuntimeError: error from cleanup ,而是按预期方式引发RuntimeError: error from throws 。我不能告诉你为什么这个没有被包含在python 2中的官方原因。这个问题自PEP 344以来就已经知道了,提到Raymond Hettinger在2003年提出这个问题。如果我不得不猜测,修复这个是一个突破性的变化(除其他外,它影响
sys.exc_info 的语义,并且这通常是一个足够的理由不在次要版本中执行它。如果你在python 2上的选项:
1)命名要重新加注的异常,并且只处理一堆或两条添加到堆栈跟踪底部的行。您的示例
nested 函数变为:
1
2
3
4
5
6
7
8
9 def nested():
try:
throws()
except BaseException as e:
try:
cleanup()
except:
pass
raise e和相关的追溯:
1
2
3
4
5
6 Traceback (most recent call last):
File"example", line 24, in main
nested()
File"example", line 17, in nested
raise e
RuntimeError: error from throws因此,回溯会被改变,但它可以工作。
1.5)使用
raise 的3参数版本。很多人都不知道这个,并且它是一种合法的(如果笨重的)保存堆栈跟踪的方法。
1
2
3
4
5
6
7
8
9
10 def nested():
try:
throws()
except:
e = sys.exc_info()
try:
cleanup()
except:
pass
raise e[0],e[1],e[2]
sys.exc_info 为我们提供了一个包含(类型,值,回溯)的3元组,这正是raise 的3参数版本所采用的。请注意,这个3-arg语法仅适用于python 2。2)重构清理代码,使其无法抛出未处理的异常。请记住,这都是关于范围的 - 将
try/except 移出nested 并移动到自己的函数中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 def nested():
try:
throws()
except:
cleanup()
raise
def cleanup():
try:
cleanup_code_that_totally_could_raise_an_exception()
except:
pass
def cleanup_code_that_totally_could_raise_an_exception():
raise RuntimeError('error from cleanup')现在你不必担心;因为异常从未进入
nested 的范围,所以它不会干扰你想要重新引用的异常。3)在你阅读所有这些内容并使用它之前,像你一样使用裸
raise ;清理代码通常不会引发异常,对吧? :-)好。