Pythoner may be familiar with exception control like the demo beblow there:
1
2
3
4
5
6
7
8
9
try:
i = 0
whileTrue:
i += 1
except KeyboardInterrupt:
print"after abort:", i
print"get here"
If you press down ctrl + c, there will trigger a interruption by keyboard. The inner infinite loop will stop and jump into the exception handler. There are another examples of exception and interruption in the computer. Programmer may also ask what’s the benefite to handle the exception …
What if there is something unpredictable and it will stop the program to run correctly?
If you do 1 divide 0 in your program, the CPU don’t know how to compute that expression. In the level of operating system, OS will handle it as an exception and tell programmer that it doesn’t work. The program must be stopped or killed.
1
2
3
4
try:
1/0
except:
print"Welcome Chapter 42 -- Guideline to galaxy"
Although there trig a exception for diveding zero, but we catch it and handle it correctly. So, the program end correctly.
The mechanism of exception handling come from operating system.
Here is the definition of exception object in Python. (Don’t forget that everything in Python is object).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[Include/pyerrors.h]
...
typedefstruct {
PyObject_HEAD
PyObject *dict;
PyObject *args;
PyObject *message;
} PyBaseExceptionObject;
...
/* Predefined exceptions */
PyAPI_DATA(PyObject *) PyExc_BaseException;
PyAPI_DATA(PyObject *) PyExc_Exception;
PyAPI_DATA(PyObject *) PyExc_StopIteration;
...
PyAPI_DATA(PyObject *) PyExc_ZeroDivisionError;
PyAPI_DATA(PyObject *) PyExc_EOFError;
...
Here is a example about how python deal with the exception.
What would happen if there is a expression 1/0. I use IPython interpreter do this demo.
1
2
3
4
5
6
7
8
9
In [18]: code = compile("1/0", "testscript", mode= "exec")
In [19]: dis.disassemble(code)
10 LOAD_CONST 0 (1)
3 LOAD_CONST 1 (0)
6 BINARY_DIVIDE
7 POP_TOP
8 LOAD_CONST 2 (None)
11 RETURN_VALUE
The assemble code (opcode) of expression 1/0 in Python is compiled into that opcode code. It isn’t difficult to understand what the LOAD_CONST do.
Let’s dig into the detail of BINARY_DIVIDE in Python/ceval.c
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
[Python/ceval.c]
for(;;)// big for loop
...
TARGET_NOARG(BINARY_DIVIDE)
{
if (!_Py_QnewFlag) {
w = POP();
v = TOP();
x = PyNumber_Divide(v, w); //
Py_DECREF(v);
Py_DECREF(w);
SET_TOP(x);
if (x != NULL) DISPATCH();
break;
}
}
...
/*
set up the basic information for the reason
why exception happened -- notes by Jason Leaster
*/
/* Quickly continue if no error occurred */
if (why == WHY_NOT) {
if (err == 0 && x != NULL) {
READ_TIMESTAMP(loop1);
continue; /* Normal, fast path */
}
why = WHY_EXCEPTION;
x = Py_None;
err = 0;
}
...
/* Log traceback info if this is a real exception */
if (why == WHY_EXCEPTION) {
PyTraceBack_Here(f);
...
}
...
[Objects/abstract.c]
BINARY_FUNC(PyNumber_Subtract, nb_subtract, "-")
/* PyNumber_Divide is just the other name of function nb_divide
and then you will find that the nb_divide is just a function pointer
in struct PyNumberMethods.
The REAL implementation is function @int_classic_div in
Objects/intobject.c. The @int_classic_div will call function
After the VM (ceval.c) know why there is a exception, the VM will take step into handling this exception. We have knew that the big for switch loop execute the opcode step by step. Once there is something wrong with the opcode at runtime, the VM will setup exception object by PyErr_SetObject. And then VM also will setup the traceback object of type PyTracebackObject.
1
2
3
4
5
6
7
8
9
/* Traceback interface */
typedefstruct _traceback {
PyObject_HEAD
struct _traceback *tb_next;
struct _frame *tb_frame;
int tb_lasti;
int tb_lineno;
} PyTracebackObject;
You may notice that the PyTracebackObject is a single direction linked-list.