1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def f():
    try:
        print 'try'
        raise NameError
    except Exception, e:
        print 'except'
        raise KeyError
    else:
        print 'else'
        raise IOError
    finally:
        print 'finally'
        raise ValueError

在以上代码中,无论try里有没有异常,走得是except还是else,最终抛出的都是finally中的ValueError。为此小记一下Python里的异常处理流程。

Python当前状态的异常信息存储在线程状态PyThreadState中,包括type, value, traceback。无论是主动raise还是bug触发,异常信息最终都会通过PyErr_Restore(Python/errors.c)被修改。

异常处理的主要流程在PyEval_EvalFrameEx(Python/ceval.c)里,也是Python虚拟机的核心处理流程。

先说明几个概念以便理解:

  1. Python虚拟机里PyEval_EvalFrameEx干的活就是物理机里CPU干的活,根据指令作出相应动作
  2. frame:一个函数的执行环境,可以理解为物理机上EBP, ESP等寄存器确定的函数调用栈,外加一点别的状态信息
  3. blockstack:也是一个栈,隶属于一个frame。每当遇到循环或者try异常处理块都会压入一个block对象,块流程完了弹出
  4. traceback:一个链表,记录发生异常时候的调用栈信息

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    
    PyObject *
    PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
    {
    
     register enum why_code why; // 每一条指令处理完之后的状态,用来标记是否有异常
    
     switch(opcode){} // 一个处理字节码指令的巨大switch
     ...
    
     // 记录traceback,用来在异常最终没有处理时打印调用栈信息
     // 这个why ==WHY_EXCEPTION条件很重要,如果在finally中发生异常,则why的值是WHY_RERAISE,因此不会进入这个条件记录新的traceback,而是直接覆盖当前的traceback,所以在最终的调用栈里,finally异常之前try/except/else中的异常信息也就被覆盖了
       if (why == WHY_EXCEPTION) {
            PyTraceBack_Here(f);
    
            if (tstate->c_tracefunc != NULL)
                call_exc_trace(tstate->c_tracefunc,
                               tstate->c_traceobj, f);
        }
    
     // 这种是finally中有异常的状态,reraise
       if (why == WHY_RERAISE)
            why = WHY_EXCEPTION;
    
     // 栈帧展开,主要为了在当前frame,沿着block栈向上寻找有没有try/except来接住异常
    
    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];
    
           ......
    
            /* Now we have to pop the block. */
            f->f_iblock--;
    
           ......
    
          // 在finally块中
            if (b->b_type == SETUP_FINALLY ||
                ......
                ) {
    
               // 有异常(raise xxxError或bug触发的情况)
                if (why == WHY_EXCEPTION) {
                    PyObject *exc, *val, *tb;
    
                    // 取出异常信息,压入执行栈
                    PyErr_Fetch(&exc, &val, &tb);
                    if (tb == NULL) {
                        Py_INCREF(Py_None);
                        PUSH(Py_None);
                    } else
                        PUSH(tb);
                    PUSH(val);
                    PUSH(exc);
                }
                else {
                    // return或者continue语句
                    if (why & (WHY_RETURN | WHY_CONTINUE))
                        PUSH(retval);
                    v = PyInt_FromLong((long)why);
                    PUSH(v);
                }
               // 跳转到END_FINALLY指令进行扫尾工作
                why = WHY_NOT;
                JUMPTO(b->b_handler);
                break;
            }
        } /* unwind stack */
    
     // 函数返回值为null说明函数中发生异常未处理
    
    if (why != WHY_RETURN)
        retval = NULL;
    
     // 栈帧回退,取回上一层函数执行信息
    
    tstate->frame = f->f_back;
    
    return retval;
    }

补充一点:

dis模块可以“反汇编”Python字节码变成“Python汇编语言”,下面就是示例代码的反汇编结果:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
  6           0 SETUP_FINALLY           63 (to 66)
              3 SETUP_EXCEPT            15 (to 21)

  7           6 LOAD_CONST               1 ('try')
              9 PRINT_ITEM
             10 PRINT_NEWLINE

  8          11 LOAD_GLOBAL              0 (NameError)
             14 RAISE_VARARGS            1
             17 POP_BLOCK
             18 JUMP_FORWARD            30 (to 51)

  9     >>   21 DUP_TOP
             22 LOAD_GLOBAL              1 (Exception)
             25 COMPARE_OP              10 (exception match)
             28 POP_JUMP_IF_FALSE       50
             31 POP_TOP
             32 STORE_FAST               0 (e)
             35 POP_TOP

 10          36 LOAD_CONST               2 ('except')
             39 PRINT_ITEM
             40 PRINT_NEWLINE

 11          41 LOAD_GLOBAL              2 (KeyError)
             44 RAISE_VARARGS            1
             47 JUMP_FORWARD            12 (to 62)
        >>   50 END_FINALLY

 13     >>   51 LOAD_CONST               3 ('else')
             54 PRINT_ITEM
             55 PRINT_NEWLINE

 14          56 LOAD_GLOBAL              3 (IOError)
             59 RAISE_VARARGS            1
        >>   62 POP_BLOCK
             63 LOAD_CONST               0 (None)

 16     >>   66 LOAD_CONST               4 ('finally')
             69 PRINT_ITEM
             70 PRINT_NEWLINE

 17          71 LOAD_GLOBAL              4 (ValueError)
             74 RAISE_VARARGS            1
             77 END_FINALLY
             78 LOAD_CONST               0 (None)
             81 RETURN_VALUE