一般来说,当异常发生时,其异常栈应该从主调用者的入口一直到异常发生点,例如Java里经常出现的长达一两页的stack trace,这其中可能存在中间层代码收到异常时,进行一些动作(关闭数据库连接或者文件等),然后再次抛出异常的情况。
Python 3中,在except块内进行处理,然后重新抛出异常即可,例如下面的测试代码:
# -*- coding: utf-8 -*-
import sys
def a():
b()
def b():
c() # call the c
def c():
raise Exception("Hello World!")
class MyException(Exception):
pass
def m_a():
m_b()
def m_b():
m_c() # call the m_c
def m_c():
try:
a()
except Exception as e:
raise e
m_a()
运行时会打印异常调用栈为:
Traceback (most recent call last):
File "test.py", line 36, in <module>
m_a()
File "test.py", line 22, in m_a
m_b()
File "test.py", line 26, in m_b
m_c() # call the m_c
File "test.py", line 33, in m_c
raise e
File "test.py", line 31, in m_c
a()
File "test.py", line 6, in a
b()
File "test.py", line 10, in b
c() # call the c
File "test.py", line 14, in c
raise Exception("Hello World!")
Exception: Hello World!
这是因为Python 3在异常对象内添加了 __traceback__ 属性,用于存放异常栈信息,并且在重抛出的时候自动追加当前调用栈。在Python 2中,相同的写法会输出下面的内容:
Traceback (most recent call last):
File "exception_test.py", line 37, in <module>
m_a()
File "exception_test.py", line 22, in m_a
m_b()
File "exception_test.py", line 26, in m_b
m_c() # call the m_c
File "exception_test.py", line 34, in m_c
raise e
Exception: Hello World!
可以看到,打印出来的异常栈内容只能追踪到 m_c 函数的 raise 语句,而引起这个异常的原因丢失掉了。
为了让Python 2也能显示像Python 3一样的异常栈,我曾经尝试过使用某个包装类将当前异常和栈保存起来,然后在Top-Level通过操作字符串来拼凑成一个完整的异常栈,但实现上不是很优雅,而且要求代码必须使用这个异常包装类才行。
后来某一天突然在Python官方文档上看见raise语句的解释:
6.9. The raise
statement
raise_stmt ::= "raise" [