以下是我在Doug Hellman网站上找到的一个名为" masking_exceptions_catch.py"的文件中的示例。我目前无法找到该链接。抛出throws()中引发的异常,同时报告由cleanup()引发的异常。
在他的文章中,Doug评论说处理是非直观的。中途期望它是Python版本中的一个错误或限制(大约在2009年),我在Mac的当前生产版本中运行它(2.7.6)。它仍然报告cleanup()的异常。我发现这有点令人惊讶,并希望看到它是如何实际正确或理想的行为。
#!/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())
节目输出:
$ 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
答案 0 :(得分:17)
绕回来回答。我首先回答你的问题。 : - )
这真的有效吗?
def f():
try:
raise Exception('bananas!')
except:
pass
raise
那么,上面做了什么? Cue Jeopardy音乐。
好吧,然后,铅笔下来。
# 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!
嗯,那是富有成效的。为了好玩,让我们尝试命名例外。
def f():
try:
raise Exception('bananas!')
except Exception as e:
pass
raise e
现在怎么办?
# 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一致。
try:
1/0
except Exception as e:
x=4
#can I access `x` here after the exception block? How about `e`?
try
和except
不是范围。实际上,很少有东西在python中;我们有" LEGB规则"记住四个命名空间 - Local,Enclosing,Global,Builtin。其他区块根本不是范围;我很高兴地在x
循环中声明for
,并期望在该循环之后仍然可以引用它。
所以,尴尬。例外是否应该特别限制在其封闭的词汇块中? Python 2说不,python 3说yes。但我在这里过分简化了事情;裸raise
是您最初询问的问题,问题密切相关,但实际上并不相同。 Python 3 可以强制命令将命名异常限定在其块中,而不解决裸raise
事。
裸raise
做什么
常见用法是使用裸raise
作为保留堆栈跟踪的方法。捕获,进行记录/清理,再加注。很酷,我的清理代码没有出现在追溯中,99.9%的时间都有效。但是当我们尝试在异常处理程序中处理嵌套异常时,事情就会向南发展。 有时候。(请参阅底部的示例,了解/它是什么时候出现问题)
直观地说,无参数raise
将正确处理嵌套的异常处理程序,并找出正确的"当前"重新加入的例外。但这并不完全是现实。事实证明 - 在此处了解实施细节 - 异常信息将保存为当前frame object的成员。而在python 2中,根本没有管道来处理单帧内堆栈上的异常处理程序;只是一个包含最后一个异常的字段,无论我们对它做了什么处理。这是raise
抓住的东西。
6.9。 The raise statement
raise_stmt ::= "raise" [expression ["," expression ["," expression]]]
如果没有表达式,则raise重新引发最后一个异常 在当前范围中处于活动状态。
所以,是的,这是python 2中与追踪信息存储方式有关的问题 - 在Highlander传统中,只能有一个(追踪对象保存到给定的堆栈帧)。因此,裸raise
再次提出当前框架认为的最后一个"异常,这不一定是我们人类大脑所认为的那个异常是我们当时所处的词汇嵌套异常块所特有的异常。呸,范围!
那么,在python 3中修复了吗?
是。怎么样? New bytecode instruction(实际上,除了处理程序之外,还有另外一个隐含的处理程序),但真正关心的是 - 这一切都只是工作"直观。您的示例代码不是按RuntimeError: error from cleanup
获得,而是按预期方式引发RuntimeError: error from throws
。
我无法正式解释为什么这不包含在python 2中。这个问题已为人所知since PEP 344,其中提到Raymond Hettinger在 2003 中提出了这个问题。如果我必须猜测,修复这个是一个重大变化(除此之外,它会影响sys.exc_info
的语义),并且这通常是一个很好的理由不这样做它是一个次要版本。
如果你在python 2上的选项:
1)命名要重新加注的异常,并且只处理一堆或两条添加到堆栈跟踪底部的行。您的示例nested
函数变为:
def nested():
try:
throws()
except BaseException as e:
try:
cleanup()
except:
pass
raise e
以及相关的追溯:
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参数版本。很多人都不知道这个,并且这是一种合法的(如果笨重的)保存堆栈跟踪的方法。
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
移出并进入自己的功能。
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
,并使用它;清理代码通常不会引发异常,对吧? : - )