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 ... 的最末尾。这里总之也检查一下有没有需要处理的异常,效果类似。

posted @ 2013-07-20 15:27  meteorx165  Views(541)  Comments(0Edit  收藏  举报