python笔记 - 异常处理中的END_FINALLY指令
理一下过程。测试用py源码:
try : raise Exception() except ZeroDivisionError, e : print 1 finally : print 2
对应字节码:
3 0 SETUP_FINALLY 44 (to 47) 3 SETUP_EXCEPT 13 (to 19) 4 6 LOAD_NAME 0 (Exception) 9 CALL_FUNCTION 0 12 RAISE_VARARGS 1 // 设置异常,然后跳到19 15 POP_BLOCK // 与 3 SETUP_EXCEPT 对应,但其实永远也走不到这里 16 JUMP_FORWARD 24 (to 43) 5 >> 19 DUP_TOP 20 LOAD_NAME 1 (ZeroDivisionError) 23 COMPARE_OP 10 (exception match) 26 POP_JUMP_IF_FALSE 42 // 没有接住的话,跳到 42 END_FINALLY 29 POP_TOP 30 STORE_NAME 2 (e) 33 POP_TOP 6 34 LOAD_CONST 0 (1) 37 PRINT_ITEM 38 PRINT_NEWLINE 7 39 JUMP_FORWARD 1 (to 43) >> 42 END_FINALLY // 与 3 SETUP_EXCEPT 对应,这里外面还有一层TryBlock(最一开始的0 SETUP_FINALLY),所以可以安然无恙继续执行;
但如果没有finally的话,这里就会break出来,这个PyFrame的执行到此为止了 >> 43 POP_BLOCK // 与 0 SETUP_FINALLY 对应,没有异常抛出的话,就在这里把上面SETUP_FINALLY的TryBlock扔掉,因此这里两个TryBlock都被Pop()掉了 44 LOAD_CONST 1 (None) 9 >> 47 LOAD_CONST 2 (2) 50 PRINT_ITEM 51 PRINT_NEWLINE 52 END_FINALLY // 与 0 SETUP_FINALLY 对应,这里再次检查栈顶以得知有无异常 53 LOAD_CONST 1 (None) 56 RETURN_VALUE
0~3:SETUP_FINALLY, SETUP_EXCEPT依次新增两个TryBlock;
6~12:这里why被设置为WHY_EXCEPTION,从超大switch(opcode)中break出去,来到下面这段:
/* Unwind stacks if a (pseudo) exception occurred */ fast_block_end: while (why != WHY_NOT && f->f_iblock > 0) { /* Peek at the current block. */ PyTryBlock *b = &f->f_blockstack[f->f_iblock - 1]; assert(why != WHY_YIELD); if (b->b_type == SETUP_LOOP && why == WHY_CONTINUE) { why = WHY_NOT; JUMPTO(PyInt_AS_LONG(retval)); Py_DECREF(retval); break; } /* Now we have to pop the block. */ f->f_iblock--; while (STACK_LEVEL() > b->b_level) { v = POP(); Py_XDECREF(v); } if (b->b_type == SETUP_LOOP && why == WHY_BREAK) { why = WHY_NOT; JUMPTO(b->b_handler); break; } if (b->b_type == SETUP_FINALLY || (b->b_type == SETUP_EXCEPT && why == WHY_EXCEPTION) || b->b_type == SETUP_WITH) { if (why == WHY_EXCEPTION) { PyObject *exc, *val, *tb; PyErr_Fetch(&exc, &val, &tb); if (val == NULL) { val = Py_None; Py_INCREF(val); } /* Make the raw exception data available to the handler, so a program can emulate the Python main loop. Don't do this for 'finally'. */ if (b->b_type == SETUP_EXCEPT || b->b_type == SETUP_WITH) { PyErr_NormalizeException(&exc, &val, &tb); set_exc_info(tstate, exc, val, tb); } if (tb == NULL) { Py_INCREF(Py_None); PUSH(Py_None); } else PUSH(tb); PUSH(val); PUSH(exc); } else { if (why & (WHY_RETURN | WHY_CONTINUE)) PUSH(retval); v = PyInt_FromLong((long)why); PUSH(v); } why = WHY_NOT; JUMPTO(b->b_handler); break; } } /* unwind stack */
这段的作用是:
1. 如果当前还有TryBlock,并且是对应的SETUP_EXCEPT(在 try ... 里面),或者对应的SETUP_FINALLY(在 finaly ... 里面),
那么即使why是WHY_EXCEPTION,也不用急着从整个for(;;)大循环中break出去,因为外面的TryBlock还有可能接住这个Exception;
2. 但如果当前已经没有TryBlock的话(已经在最外层),那么会接着走到下一段:
/* End the loop if we still have an error (or return) */ if (why != WHY_NOT) break;
PyEval_EvalFrameEx()返回NULL给上一层调用者。
总结一下,END_FINALLY会在两个地方出现:
1. except ... 的最末尾。如果except没有匹配上抛出的异常的话,那么会执行END_FINALLY检查当前PyFrame是否应该被中止执行,返回上一个PyFrame处理异常(有没有finally,或者外面还有没有try);
2. finally ... 的最末尾。这里总之也检查一下有没有需要处理的异常,效果类似。