当代码中发生异常而又没有被当前的 frame 捕获时, Python 会将异常一直往上传递, 直到被某个 frame 中的代码捕获, 如果最终仍然没有一个 frame 捕获异常, 那么 Python 的入口点(不同的启动方式有不同的入口点, 例 如执行文件, 交互式 shell等等) 会打印出异常信息并退出.
异常对象形成一个单向链表, 越靠近头部的节点越处于调用链(call frame)的上层, 越靠近尾部的节点越接近异常发生的地方.
测试的 Python 代码:
try:
x = 1 / 0
except ZeroDivisionError as e:
print(e)使用 dis 查看对应的字节码:
1 0 SETUP_FINALLY 12 (to 14)
2 2 LOAD_CONST 0 (1)
4 LOAD_CONST 1 (0)
6 BINARY_TRUE_DIVIDE
8 STORE_NAME 0 (x)
10 POP_BLOCK
12 JUMP_FORWARD 44 (to 58)
3 >> 14 DUP_TOP
16 LOAD_NAME 1 (ZeroDivisionError)
18 JUMP_IF_NOT_EXC_MATCH 56
20 POP_TOP
22 STORE_NAME 2 (e)
24 POP_TOP
26 SETUP_FINALLY 20 (to 48)
4 28 LOAD_NAME 3 (print)
30 LOAD_NAME 2 (e)
32 CALL_FUNCTION 1
34 POP_TOP
36 POP_BLOCK
38 POP_EXCEPT
40 LOAD_CONST 2 (None)
42 STORE_NAME 2 (e)
44 DELETE_NAME 2 (e)
46 JUMP_FORWARD 10 (to 58)
>> 48 LOAD_CONST 2 (None)
50 STORE_NAME 2 (e)
52 DELETE_NAME 2 (e)
54 RERAISE
>> 56 RERAISE
>> 58 LOAD_CONST 2 (None)
60 RETURN_VALUE
和异常处理相关的字节码有:
-
SETUP_FINALLY
-
JUMP_IF_NOT_EXC_MATCH
-
RERAISE
-
POP_EXCEPT
SETUP_FINALLY 对应的处理器:
case TARGET(SETUP_FINALLY): {
PyFrame_BlockSetup(f, SETUP_FINALLY, INSTR_OFFSET() + oparg,
STACK_LEVEL());
DISPATCH();
}void
PyFrame_BlockSetup(PyFrameObject *f, int type, int handler, int level)
{
PyTryBlock *b;
if (f->f_iblock >= CO_MAXBLOCKS)
Py_FatalError("XXX block stack overflow");
b = &f->f_blockstack[f->f_iblock++];
b->b_type = type;
b->b_level = level;
b->b_handler = handler;
}typedef struct {
int b_type; /* what kind of block this is */
int b_handler; /* where to jump to find handler, b_handler 其实就是跳转地址 */
int b_level; /* value stack level to pop to, b_level 保存当前栈顶的位置 */
} PyTryBlock;PyTryBlock 保存了异常处理的重要信息, 当异常发生时可以从 PyTryBlock 得知异常处理的代码的位置.
BINARY_TRUE_DIVIDE 的处理器如下:
case TARGET(BINARY_TRUE_DIVIDE): {
PyObject *divisor = POP();
PyObject *dividend = TOP();
PyObject *quotient = PyNumber_TrueDivide(dividend, divisor);
Py_DECREF(dividend);
Py_DECREF(divisor);
SET_TOP(quotient);
if (quotient == NULL)
goto error;
DISPATCH();
}PyNumber_TrueDivide 最终会调用整数对象的除法函数:
static PyObject *
long_true_divide(PyObject *v, PyObject *w)
{
if (b_size == 0) {
PyErr_SetString(PyExc_ZeroDivisionError,
"division by zero");
goto error;
}
error:
return NULL;
}PyErr_SetString 会设置 tstate->curexc_type, tstate->curexc_value 和 tstate->curexc_traceback. 这三个变量记录了当前异常的信息.
PyThreadState 中保存着异常相关的信息:
struct _ts {
/* The exception currently being raised */
PyObject *curexc_type;
PyObject *curexc_value;
PyObject *curexc_traceback;
}-
curexc_type, 异常类型.
-
curexc_value, 异常信息.
-
curexc_traceback, PyTracebackObject 对象.
执行流程最终回到 ceval, quotient 等于 NULL, 跳转到处理异常的代码.
/* Log traceback info. */
PyTraceBack_Here(f);
int
PyTraceBack_Here(PyFrameObject *frame)
{
PyObject *exc, *val, *tb, *newtb;
PyErr_Fetch(&exc, &val, &tb);
newtb = _PyTraceBack_FromFrame(tb, frame);
if (newtb == NULL) {
_PyErr_ChainExceptions(exc, val, tb);
return -1;
}
PyErr_Restore(exc, val, newtb);
Py_XDECREF(tb);
return 0;
}PyTraceBack_Here 生成新的 PyTracebackObject 对象.
typedef struct _traceback {
PyObject_HEAD
struct _traceback *tb_next;
struct _frame *tb_frame;
int tb_lasti;
int tb_lineno;
} PyTracebackObject;
static PyObject *
tb_create_raw(PyTracebackObject *next, PyFrameObject *frame, int lasti,
int lineno)
{
PyTracebackObject *tb;
if ((next != NULL && !PyTraceBack_Check(next)) ||
frame == NULL || !PyFrame_Check(frame)) {
PyErr_BadInternalCall();
return NULL;
}
tb = PyObject_GC_New(PyTracebackObject, &PyTraceBack_Type);
if (tb != NULL) {
Py_XINCREF(next);
tb->tb_next = next;
Py_XINCREF(frame);
tb->tb_frame = frame;
tb->tb_lasti = lasti;
tb->tb_lineno = lineno;
PyObject_GC_Track(tb);
}
return (PyObject *)tb;
}PyTracebackObject 对象记录了异常发生时的信息, 例如 frame, 发生异常的代码所在的行号和字节码地址. 这些信息也就是当 Python 解释器因为异常退出时在终端打印的异常栈信息.
exception_unwind:
/* Unwind stacks if an exception occurred */
while (f->f_iblock > 0) {
/* Pop the current block. */
PyTryBlock *b = &f->f_blockstack[--f->f_iblock];
if (b->b_type == EXCEPT_HANDLER) {
// 取出栈上的异常信息
// exc_info->exc_type = POP();
// exc_info->exc_value = POP();
// exc_info->exc_traceback = POP();
UNWIND_EXCEPT_HANDLER(b);
continue;
}
// 清空栈中在 b->b_level 上面的元素
// 例如, 如果 b->b_type 等于 SETUP_FINALLY, 那么清空自 SETUP_FINALLY 指令之后栈上新增的元素.
UNWIND_BLOCK(b);
if (b->b_type == SETUP_FINALLY) {
PyObject *exc, *val, *tb;
int handler = b->b_handler;
_PyErr_StackItem *exc_info = tstate->exc_info;
/* Beware, this invalidates all b->b_* fields */
PyFrame_BlockSetup(f, EXCEPT_HANDLER, -1, STACK_LEVEL());
// 保存上一个异常信息
PUSH(exc_info->exc_traceback);
PUSH(exc_info->exc_value);
if (exc_info->exc_type != NULL) {
PUSH(exc_info->exc_type);
}
else {
Py_INCREF(Py_None);
PUSH(Py_None);
}
_PyErr_Fetch(tstate, &exc, &val, &tb);
/* Make the raw exception data
available to the handler,
so a program can emulate the
Python main loop. */
_PyErr_NormalizeException(tstate, &exc, &val, &tb);
// 设置异常的 traceback 属性.
if (tb != NULL)
PyException_SetTraceback(val, tb);
else
PyException_SetTraceback(val, Py_None);
// 保存当前异常信息到 tstate->exc_info
Py_INCREF(exc);
exc_info->exc_type = exc;
Py_INCREF(val);
exc_info->exc_value = val;
exc_info->exc_traceback = tb;
if (tb == NULL)
tb = Py_None;
Py_INCREF(tb);
// 将异常信息放入栈上, except 代码块需要使用这些异常信息和 except 语句中的异常进行对比.
PUSH(tb);
PUSH(val);
PUSH(exc);
JUMPTO(handler);
if (_Py_TracingPossible(ceval2)) {
int needs_new_execution_window = (f->f_lasti < instr_lb || f->f_lasti >= instr_ub);
int needs_line_update = (f->f_lasti == instr_lb || f->f_lasti < instr_prev);
/* Make sure that we trace line after exception if we are in a new execution
* window or we don't need a line update and we are not in the first instruction
* of the line. */
if (needs_new_execution_window || (!needs_line_update && instr_lb > 0)) {
instr_prev = INT_MAX;
}
}
/* Resume normal execution */
goto main_loop;
}
} /* unwind stack */如果没有 except 可以捕获异常, 那么最后该异常会被重新触发. RERAISE 的作用就是重新触发异常.
例如, 如果在处理异常 A 时, 有发生了异常 B, 那么 Python 内部会记录下这种嵌套异常的关系. 异常对象中有一个 context 属性, 通过该 context 便可以知道是否发生了嵌套的异常.
/* exception 是异常类, value 一般是一个字符串, 表示错误信息.
主要作用是调用 _PyErr_Restore(tstate, exception, value, tb) 记录下该异常信息.
如果该异常是嵌套异常, 那么设置该异常的 context 属性, PyException_SetContext(value, exc_value).
*/
void
_PyErr_SetObject(PyThreadState *tstate, PyObject *exception, PyObject *value)
{
PyObject *exc_value;
PyObject *tb = NULL;
if (exc_value != NULL && exc_value != Py_None) {
/* Implicit exception chaining */
Py_INCREF(exc_value);
if (value == NULL || !PyExceptionInstance_Check(value)) {
/* We must normalize the value right now */
PyObject *fixed_value;
/* Issue #23571: functions must not be called with an
exception set */
_PyErr_Clear(tstate);
fixed_value = _PyErr_CreateException(exception, value);
Py_XDECREF(value);
if (fixed_value == NULL) {
Py_DECREF(exc_value);
return;
}
value = fixed_value;
}
/* Avoid reference cycles through the context chain.
This is O(chain length) but context chains are
usually very short. Sensitive readers may try
to inline the call to PyException_GetContext. */
/* 异常对象的 context 等于上一个异常对象,
例如在处理异常 A 的 except 代码块中又发生了异常 B, 那么异常 B 的 context 就是异常 A
*/
if (exc_value != value) {
PyObject *o = exc_value, *context;
while ((context = PyException_GetContext(o))) {
Py_DECREF(context);
if (context == value) {
PyException_SetContext(o, NULL);
break;
}
o = context;
}
PyException_SetContext(value, exc_value); // 设置 context
}
else {
Py_DECREF(exc_value);
}
}
}之前一直不清楚 Python 内部是如何实现异常处理的, 通过阅读源码终于弄懂了内部的实现原理.
当 ceval 执行字节码遇到错误时, 会调用 PyErr_SetString(PyObject *exception, const char *string) 设置异常信息, 异常信息保存在线程对象中, 具体是 tstate->curexc_type, tstate->curexc_value和 tstate->curexc_traceback, 然后跳出 switch case 来到错误处理的代码.
如果错误发生在 try/except 中, 那么通过 PyTryBlock 可以知道处理异常的代码的地址, 将异常信息(tstate->curexc_*) 保存到 tstate->exc_info, 以及栈上, 然后重置 tstate->curexc_*, 最后跳转到处理异常的代码. 如果没有匹配的 except, 那么异常会被重新触发, 重复异常处理的流程.
如果在当前栈帧没有捕获异常, 那么异常会上溯到上一层栈帧, 直到有一个栈帧捕获异常或退出程序.
如果错误没有被捕获, 那么最终会终止程序, 打印异常栈信息.