Python虚拟机之异常控制流(五)
Python中的异常控制语义结构
在Python虚拟机之异常控制流(四)这一章中,我们考察了Python的异常在虚拟机中的级别上是什么东西,抛出异常这个动作在虚拟机的级别上对应的行为,最后,我们还剖析了Python在处理异常时栈帧展开行为。但遗憾的是,在前面,我们只考察了Python虚拟机中内建的处理异常的动作,并没有使用Python语言提供的异常控制结构。本章我们将研究Python语言提供的异常控制结构如何影响Python虚拟机的异常处理流程
# cat demo6.py
try:
raise Exception("I am an exception")
except Exception, e:
print(e)
finally:
print("The finally code")
# python2.5
……
>>> source = open("demo6.py").read()
>>> co = compile(source, "demo6.py", "exec")
>>> import dis
>>> dis.dis(co)
1 0 SETUP_FINALLY 49 (to 52)
3 SETUP_EXCEPT 16 (to 22) 2 6 LOAD_NAME 0 (Exception)
9 LOAD_CONST 0 ('I am an exception')
12 CALL_FUNCTION 1
15 RAISE_VARARGS 1
18 POP_BLOCK
19 JUMP_FORWARD 26 (to 48) 3 >> 22 DUP_TOP
23 LOAD_NAME 0 (Exception)
26 COMPARE_OP 10 (exception match)
29 JUMP_IF_FALSE 14 (to 46)
32 POP_TOP
33 POP_TOP
34 STORE_NAME 1 (e)
37 POP_TOP 4 38 LOAD_NAME 1 (e)
41 PRINT_ITEM
42 PRINT_NEWLINE
43 JUMP_FORWARD 2 (to 48)
>> 46 POP_TOP
47 END_FINALLY
>> 48 POP_BLOCK
49 LOAD_CONST 1 (None) 6 >> 52 LOAD_CONST 2 ('The finally code')
55 PRINT_ITEM
56 PRINT_NEWLINE
57 END_FINALLY
58 LOAD_CONST 1 (None)
61 RETURN_VALUE
开始的两条字节码指令似曾相识,其实前面剖析Python循环结构的时候,我们曾说过,SETUP_FINALLY和SETUP_EXCEPT一样,它们都是调用PyFrame_BlockSetup,我们再来看一下PyFrame_BlockSetup的代码
frameobject.c
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;
}
frameobject.h
typedef struct _frame {
……
int f_iblock; /* index in f_blockstack */
PyTryBlock f_blockstack[CO_MAXBLOCKS]; /* for try and loop blocks */
……
} PyFrameObject;
frameobject.h
typedef struct {
int b_type; /* what kind of block this is */
int b_handler; /* where to jump to find handler */
int b_level; /* value stack level to pop to */
} PyTryBlock;
可以看出,SETUP_FINALLY和SETUP_EXCEPT两条指令不过是从f_blockstack中分两块走出,如图1-1
图1-1 SETUP_FINALLY和SETUP_EXCEPT完成后的的f_blockstack
在这里分出两块,肯定是要在捕捉异常时使用。不过不用心急,让我们先回到抛出异常的地方"15 RAISE_VARARGS 1"。在RAISE_VARARGS指令之前,通过"LOAD_NAME 0"、"LOAD_CONST 0"、"CALL_FUNCTION 1"构造出一个异常对象,并将此异常对象压入运行时栈中,而RAISE_VARARGS指令的工作就是把这个异常对象从运行时栈中取出
ceval.c
case RAISE_VARARGS:
u = v = w = NULL;
switch (oparg)
{
case 3:
u = POP(); /* traceback */
/* Fallthrough */
case 2:
v = POP(); /* value */
/* Fallthrough */
case 1:
w = POP(); /* exc */
case 0: /* Fallthrough */
why = do_raise(w, v, u);
break;
default:
PyErr_SetString(PyExc_SystemError,
"bad RAISE_VARARGS oparg");
why = WHY_EXCEPTION;
break;
}
break;
这里RAISE_VARARGS指令的参数是1,所以直接将异常对象取出赋值给w,然后调用do_raise函数。在do_raise中,最终将调用之前剖析过的PyErr_Restore函数,将异常对象存储在当前线程的状态对象中。在do_raise的最后,返回了一个WHY_EXCEPTION,这就是why变量的最终状态。在此之后,Python虚拟机通过一个break跳出了分发字节指令的巨大的switch语句。一旦结束了字节码指令的分发,异常的捕捉动作就有条不紊地展开了。在经过了一系列繁复的动作后(其中包括创建并设置traceback对象),Python虚拟机将携带着(why=WHY_EXCEPTION,f_iblock=2)的信息抵达真正捕获异常的代码
ceval.c
PyObject * PyEval_EvalFrameEx(PyFrameObject *f, int throwflag)
{
……
for (;;)
{
……
//巨大的switch语句
switch (opcode)
{
……
}
……
while (why != WHY_NOT && f->f_iblock > 0)
{
//[1]:获得SETUP_EXCEPT指令创建的PyTryBlock
PyTryBlock *b = PyFrame_BlockPop(f);
……
if (b->b_type == SETUP_FINALLY ||
(b->b_type == SETUP_EXCEPT &&
why == WHY_EXCEPTION))
{
if (why == WHY_EXCEPTION)
{
PyObject *exc, *val, *tb;
//[2]:获得线程状态对象中的异常信息
PyErr_Fetch(&exc, &val, &tb);
……
if (tb == NULL)
{
……
}
else
PUSH(tb);
PUSH(val);
PUSH(exc);
}
else
{
……
}
why = WHY_NOT;
JUMPTO(b->b_handler);
break;
}
}
……
//尝试捕捉异常
if (why != WHY_NOT)//[3]:不存在异常处理代码,展开堆栈
break;
……
}
……
}
在上面的代码的[1]处,Python虚拟机首先从当前的PyFrameObject对象中的f_blockstack中弹出一个PyTryBlock来,在图1-1可以看到,弹出的这个是b_type=SETUP_EXCEPT、b_handler=22的PyTryBlock。另一方面,在代码的[2]处,Python虚拟机通过PyErr_Fetch得到当前线程状态对象中存储的最新的异常对象和traceback对象:
errors.c
void PyErr_Fetch(PyObject **p_type, PyObject **p_value, PyObject **p_traceback)
{
PyThreadState *tstate = PyThreadState_GET(); *p_type = tstate->curexc_type;
*p_value = tstate->curexc_value;
*p_traceback = tstate->curexc_traceback; tstate->curexc_type = NULL;
tstate->curexc_value = NULL;
tstate->curexc_traceback = NULL;
}
随后,Python虚拟机将tb、val、exc分别压入运行时栈中,并将why设置为WHY_NOT。这又引出一个问题,为什么是WHY_NOT?不是已经发生异常了吗?没错,确实应该是WHY_NOT,不要忘了,why被设置为WHY_NOT是发生在if (b->b_type == SETUP_FINALLY || (b->b_type == SETUP_EXCEPT &&why == WHY_EXCEPTION))这个语句里,Python虚拟机意识到程序员要捕捉这个异常,所以将WHY原先的状态WHY_EXCEPTION转为WHY_NOT,代表程序从异常状态转为正常状态
而接下来的异常处理工作,则需要交给程序员指定的代码来处理,这个动作通过JUMPTO(b->b_handler)来完成。JUMPTO其实仅仅是进行了一下指令的跳跃,将Python虚拟机将要执行的下一条指令设置为异常处理代码编译后所得到的第一条字节码指令
在demo6.py中,SETUP_EXCEPT所创建的PyTryBlock中的b_handler为22,这时Python虚拟机将要执行的下一条指令就是偏移量为22的那条指令,从demo6.py的编译结果来看,正好就是"22 DUP_TOP",异常处理代码的第一条字节码指令
在处理异常的字节码指令时,有一条"26 COMPARE_OP 10"指令,这条指令将比较运行时栈中的那个被捕捉到的异常是否跟except表达式中指定的异常匹配。随后通过一条进行指令跳跃的字节码指令"29 JUMP_IF_FALSE 14 (to 46)"来判断是否需要进行指令跳跃。如果COMPARE_OP的操作结果发现异常匹配,那么JUMP_IF_FALSE就不会进行指令跳跃,而是接着执行"print(e)"表达式对应的字节码指令。而如果COMPARE_OP发现异常不匹配,那么JUMP_IF_FALSE将跳跃到偏移量为46的字节码指令:POP_TOP,那么就会忽略原先"print(e)"表达式对应的字节码指令
当异常不匹配时,会比异常匹配时多执行两条字节码指令"46 POP_TOP"和"47 END_FINALLY "。前面提到,在进入except表达式之前,Python虚拟机从当前线程的状态对象中将当前的异常信息取出,分别为tb、val和exc,并将其分别压入运行时栈中。当异常匹配时,它们都会从栈中取出并处理掉,如果异常不匹配,就是说虽然有except表达式,但实际上并没有处理异常的代码。那么已经取出的异常信息显然不能扔掉,而要重新放回线程状态对象中,然后重新设置why的状态,让Python虚拟机的内部状态重新进入到“异常发生”这种状态,并开始栈帧展开的动作,寻找真正能处理异常的代码。所以这两条指令就是完成这个“重返异常状态”的使命,从END_FINALLY指令的实现代码可以清晰看到这一点
ceval.c
case END_FINALLY:
v = POP();
if (PyInt_Check(v))
{
why = (enum why_code)PyInt_AS_LONG(v);
assert(why != WHY_YIELD);
if (why == WHY_RETURN ||
why == WHY_CONTINUE)
retval = POP();
}
else if (PyExceptionClass_Check(v) || PyString_Check(v))
{
w = POP();
u = POP();
PyErr_Restore(v, w, u);
why = WHY_RERAISE;
break;
}
else if (v != Py_None)
{
PyErr_SetString(PyExc_SystemError,
"'finally' pops bad exception");
why = WHY_EXCEPTION;
}
Py_DECREF(v);
break;
Python虚拟机通过PyErr_Restore重新设置了异常信息,并把why的状态设置为WHY_RERAISE了。
不管异常是否匹配,最终处理异常的两条岔路都会在"48 POP_BLOCK"会合
ceval.c
case POP_BLOCK:
{
PyTryBlock *b = PyFrame_BlockPop(f);
while (STACK_LEVEL() > b->b_level)
{
v = POP();
Py_DECREF(v);
}
}
continue;
这里将当前的PyFrameObject的f_blockstack中剩下的那个与SETUP_FINALLY对应的PyTryBlock弹出,然后Python虚拟机的流程进入了与finally表达式对应的字节码指令了
这里我们总结一下,Python的异常机制的实现中,最重要的就是why所表示的虚拟机状态及PyFrameObject对象中f_blockstack里存放的PyTryBlock对象了。变量why将指示Python虚拟机当前是否发生了异常,而PyTryBlock对象则指示了程序员是否为异常设置了except代码块和finally代码块。Python虚拟机处理异常的过程就是在why和PyTryBlock的共同作用下完成的。在图1-2中给出了Python中实现异常机制的详细流程:
图1-2 Python中异常机制的流程图
Python虚拟机之异常控制流(五)的更多相关文章
- Python虚拟机之异常控制流(四)
Python虚拟机中的异常控制流 先前,我们分别介绍了Python虚拟机之if控制流(一).Python虚拟机之for循环控制流(二)和Python虚拟机之while循环控制结构(三).这一章,我们来 ...
- 《python解释器源码剖析》第11章--python虚拟机中的控制流
11.0 序 在上一章中,我们剖析了python虚拟机中的一般表达式的实现.在剖析一遍表达式是我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化.但是显然这是不够的,因为怎么能没有流程控制呢 ...
- Python虚拟机之if控制流(一)
Python虚拟机中的if控制流 在所有的编程语言中,if控制流是最简单也是最常用的控制流语句.下面,我们来分析一下在Python虚拟机中对if控制流的实现 # cat demo.py a = 1 i ...
- Python虚拟机之for循环控制流(二)
Python虚拟机中的for循环控制流 在Python虚拟机之if控制流(一)这一章中,我们了解if控制流的字节码实现,在if控制结构中,虽然Python虚拟机会在不同的分支摇摆,但大体还是向前执行, ...
- Python虚拟机之while循环控制结构(三)
Python虚拟机中的while循环控制结构 在Python虚拟机之if控制流(一)和Python虚拟机之for循环控制流(二)两个章节中,我们介绍了if和for两个控制结构在Python虚拟机中的实 ...
- python虚拟机中的异常流控制
异常:对程序运行中的非正常情况进行抽象.并且提供相应的语法结构和语义元素,使得程序员能够通过这些语法结构和语义元素来方便地描述异常发生时的行为. 1.Python中的异常机制: 1.1Python虚拟 ...
- Python虚拟机类机制之从class对象到instance对象(五)
从class对象到instance对象 现在,我们来看看如何通过class对象,创建instance对象 demo1.py class A(object): name = "Python&q ...
- Python虚拟机函数机制之位置参数的默认值(五)
位置参数的默认值 在Python中,允许函数的参数有默认值.假如函数f的参数value的默认值是1,在我们调用函数时,如果传递了value参数,那么f调用时value的值即为我们传递的值,如果调用时没 ...
- 虚拟机:Python虚拟机的基本了解
探索某个东西,我们需要知道这个东西是用来干什么的,能给我们带来什么,解决了什么样的问题,有什么优缺点等等:简要了解了一下Python虚拟机的特征: 目前有几个疑问: 1.对象 · Python通过对象 ...
随机推荐
- return void ajax
public class UserInfo { private String name; private Integer age; public String getName() { return n ...
- Redis list(列表)
Redis列表是简单的字符串列表,列表是有序的,列表中的元素可以重复. 可以添加一个元素到列表的头部(左边)或者尾部(右边) 一个列表最多可以包含 232 - 1 个元素 (40多亿). 1.lpus ...
- form-data、x-www-form-urlencoded、raw、binary的区别
1.form-data: 就是http请求中的multipart/form-data,它会将表单的数据处理为一条消息,以标签为单元,用分隔符分开.既可以上传键值对,也可以上传文件.当上传的字段是文件时 ...
- str中的join方法,fromkeys(),set集合,深浅拷贝(重点)
一丶对之前的知识点进行补充 1.str中的join方法.把列表转换成字符串 # 将列表转换成字符串,每个元素之间用_拼接 s = "_".join(["天",& ...
- Outlook Web App 客户端超时设置
这篇文章我们讨论一下,OWA 2013在公共和私人的电脑是如何启用和配置. Exchange 2013 Outlook Web App (OWA) 登录页不再允许用户选择无论他们正在使用公共的或私人的 ...
- BandwagonHost 5个数据中心/机房Ping速度测试亲自体验
我们选择Bandwagonhost服务器的原因之一在于有5个数据中心,而且与众多其他VPS不同之处在于可以自己后台切换机房和IP,这样我们 在遇到不满意的速度时候,可以自己切换其他机房更换,而且对于有 ...
- javaScript的注释、变量和基本数据类型
上一级写了javaScript是用来操作文档对象元素的,这一次带大家看看javaScriput的注释.变量和基本数据类型. 1.注释:注释是什么呢?注释其实就是阻止浏览器解析某一行或者多行代码或描述的 ...
- 用代码判断当前系统是否支持某个版本的feature
JDK9已经出来有一段时间了,因此很多流行的Java应用纷纷增添了对JDK9乃至JDK10的支持,比如Tomcat. 我们通过这个链接下载最新的Tomcat源文件包,总共7MB: https://to ...
- 【UML】使用环境(转)
http://blog.csdn.net/sds15732622190/article/details/49404169 用例图 用例图是在需求文档中使用的,但一定要配合用例一同使用. ...
- android上部署tensorflow
https://www.jianshu.com/p/ddeb0400452f 按照这个博客就可以 https://github.com/CrystalChen1017/TSFOnAndroid 这个博 ...