Python虚拟机之if控制流(一)
Python虚拟机中的if控制流
在所有的编程语言中,if控制流是最简单也是最常用的控制流语句。下面,我们来分析一下在Python虚拟机中对if控制流的实现
# cat demo.py
a = 1
if a > 10:
print("a>10")
elif a <= -2:
print("a<=-2")
elif a != 1:
print("a!=1")
elif a == 1:
print("a==1")
else:
print("Unknown a")
我们先扫一下demo.py这个文件,这是一个非常简单的程序,我们的关注点并不在这个程序的本身,而是程序编译后的符号表、常量表、字节码、以及字节码中的实现
首先,我们先来看一下符号表和常量表
# python2.5
……
>>> source = open("demo.py").read()
>>> co = compile(source, "demo.py", "exec")
>>> co.co_names
('a',)
>>> co.co_consts
(1, 10, 'a>10', -2, 'a<=-2', 'a!=1', 'a==1', 'Unknown a', None)
其次是字节码
>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (a) 2 6 LOAD_NAME 0 (a)
9 LOAD_CONST 1 (10)
12 COMPARE_OP 4 (>)
15 JUMP_IF_FALSE 9 (to 27)
18 POP_TOP 3 19 LOAD_CONST 2 ('a>10')
22 PRINT_ITEM
23 PRINT_NEWLINE
24 JUMP_FORWARD 72 (to 99)
>> 27 POP_TOP 4 28 LOAD_NAME 0 (a)
31 LOAD_CONST 3 (-2)
34 COMPARE_OP 1 (<=)
37 JUMP_IF_FALSE 9 (to 49)
40 POP_TOP 5 41 LOAD_CONST 4 ('a<=-2')
44 PRINT_ITEM
45 PRINT_NEWLINE
46 JUMP_FORWARD 50 (to 99)
>> 49 POP_TOP 6 50 LOAD_NAME 0 (a)
53 LOAD_CONST 0 (1)
56 COMPARE_OP 3 (!=)
59 JUMP_IF_FALSE 9 (to 71)
62 POP_TOP 7 63 LOAD_CONST 5 ('a!=1')
66 PRINT_ITEM
67 PRINT_NEWLINE
68 JUMP_FORWARD 28 (to 99)
>> 71 POP_TOP 8 72 LOAD_NAME 0 (a)
75 LOAD_CONST 0 (1)
78 COMPARE_OP 2 (==)
81 JUMP_IF_FALSE 9 (to 93)
84 POP_TOP 9 85 LOAD_CONST 6 ('a==1')
88 PRINT_ITEM
89 PRINT_NEWLINE
90 JUMP_FORWARD 6 (to 99)
>> 93 POP_TOP 11 94 LOAD_CONST 7 ('Unknown a')
97 PRINT_ITEM
98 PRINT_NEWLINE
>> 99 LOAD_CONST 8 (None)
102 RETURN_VALUE
>>>
咋一看,长长的一串字节码,有点头晕。没关系,现在让我们将字节码和源码对应上
a = 1
"""
0 LOAD_CONST 0 (1)
3 STORE_NAME 0 (a)
"""
if a > 10:
"""
6 LOAD_NAME 0 (a)
9 LOAD_CONST 1 (10)
12 COMPARE_OP 4 (>)
15 JUMP_IF_FALSE 9 (to 27)
18 POP_TOP
"""
print("a>10")
"""
19 LOAD_CONST 2 ('a>10')
22 PRINT_ITEM
23 PRINT_NEWLINE
24 JUMP_FORWARD 72 (to 99)
>> 27 POP_TOP
"""
elif a <= -2:
"""
28 LOAD_NAME 0 (a)
31 LOAD_CONST 3 (-2)
34 COMPARE_OP 1 (<=)
37 JUMP_IF_FALSE 9 (to 49)
40 POP_TOP
"""
print("a<=-2")
"""
41 LOAD_CONST 4 ('a<=-2')
44 PRINT_ITEM
45 PRINT_NEWLINE
46 JUMP_FORWARD 50 (to 99)
>> 49 POP_TOP
"""
elif a != 1:
"""
50 LOAD_NAME 0 (a)
53 LOAD_CONST 0 (1)
56 COMPARE_OP 3 (!=)
59 JUMP_IF_FALSE 9 (to 71)
62 POP_TOP
"""
print("a!=1")
"""
63 LOAD_CONST 5 ('a!=1')
66 PRINT_ITEM
67 PRINT_NEWLINE
68 JUMP_FORWARD 28 (to 99)
>> 71 POP_TOP
"""
elif a == 1:
"""
72 LOAD_NAME 0 (a)
75 LOAD_CONST 0 (1)
78 COMPARE_OP 2 (==)
81 JUMP_IF_FALSE 9 (to 93)
84 POP_TOP
"""
print("a==1")
"""
85 LOAD_CONST 6 ('a==1')
88 PRINT_ITEM
89 PRINT_NEWLINE
90 JUMP_FORWARD 6 (to 99)
>> 93 POP_TOP
"""
else:
print("Unknown a")
"""
94 LOAD_CONST 7 ('Unknown a')
97 PRINT_ITEM
98 PRINT_NEWLINE
>> 99 LOAD_CONST 8 (None)
102 RETURN_VALUE
"""
仔细观察每个if语句包括elif,可以发现它们都有相同的套路:
- 执行LOAD_NAME指令,从local名字空间中获得变量名a所对应的值
- 执行LOAD_CONST指令,从常量表consts中读取参与该分支判断操作的常量对象
- 执行COMPARE_OP指令,对前面两条指令取得的变量值和变量对象进行比较操作
- 执行JUMP_IF_FALSE指令,根据COMPARE_OP指令的运行结果进行字节码指令的跳跃,如果if语句中包含not关键字,字节码则为JUMP_IF_TRUE
- 执行POP_TOP指令,将之前存入运行时栈的比较结果弹出栈
LOAD_NAME和LOAD_CONST这两个指令没必要再解释,如果有疑问的同学可以去看我的博客Python虚拟机中的一般表达式(一)这一章,下面我们重点来分析一下COMPARE_OP这个指令的实现:
ceval.c
case COMPARE_OP:
w = POP();
v = TOP();
//[1]:PyIntObject对象的快速通道
if (PyInt_CheckExact(w) && PyInt_CheckExact(v)) {
/* INLINE: cmp(int, int) */
register long a, b;
register int res;
a = PyInt_AS_LONG(v);
b = PyInt_AS_LONG(w);
//根据字节码指令的指令参数选择不同的比较操作
switch (oparg) {
case PyCmp_LT: res = a < b; break;
case PyCmp_LE: res = a <= b; break;
case PyCmp_EQ: res = a == b; break;
case PyCmp_NE: res = a != b; break;
case PyCmp_GT: res = a > b; break;
case PyCmp_GE: res = a >= b; break;
case PyCmp_IS: res = v == w; break;
case PyCmp_IS_NOT: res = v != w; break;
default: goto slow_compare;
}
x = res ? Py_True : Py_False;
Py_INCREF(x);
}
else {
//[2]:一般对象的慢速通道
slow_compare:
x = cmp_outcome(oparg, v, w);
}
Py_DECREF(v);
Py_DECREF(w);
//将比较结果压入运行时栈
SET_TOP(x);
if (x == NULL) break;
PREDICT(JUMP_IF_FALSE);
PREDICT(JUMP_IF_TRUE);
continue;
正如在Python虚拟机中的一般表达式(三)中的BINARY_ADD指令一样,Python虚拟机在COMPARE_OP指令的实现中为PyIntObject对象建立了快速通道。如果参与比较操作的两个对象都是PyIntObject对象,直接取得PyIntObject对对应的整数进行比较即可。从上述代码可以看出,Python正是通过COMPARE_OP指令的不同指令参数来选择不同的比较操作
如果两个对象有其一不是PyIntObject对象,那么很不幸,Python虚拟机只能进入比较操作的慢速通道,调用cmp_outcome方法进行常规的比较,性能远不如为PyIntObject建立的快速通道。现在,让我们看一下cmp_outcome的实现
ceval.c
static PyObject *
cmp_outcome(int op, register PyObject *v, register PyObject *w)
{
int res = 0;
switch (op) {
case PyCmp_IS:
res = (v == w);
break;
case PyCmp_IS_NOT:
res = (v != w);
break;
case PyCmp_IN:
res = PySequence_Contains(w, v);
if (res < 0)
return NULL;
break;
case PyCmp_NOT_IN:
res = PySequence_Contains(w, v);
if (res < 0)
return NULL;
res = !res;
break;
case PyCmp_EXC_MATCH:
res = PyErr_GivenExceptionMatches(v, w);
break;
default:
return PyObject_RichCompare(v, w, op);
}
v = res ? Py_True : Py_False;
Py_INCREF(v);
return v;
}
cmp_outcome的实现中,可以看到Python的COMPARE_OP指令不仅管辖着两个之间之间的比较操作,而且还覆盖了对象与集合之间关系的判断操作
# cat demo2.py
lst = [1, 2, 3]
if 1 in lst:
print("Found") # python2.5
……
>>> source = open("demo2.py").read()
>>> co = compile(source, "demo2.py", "exec")
>>> import dis
>>> dis.dis(co)
1 0 LOAD_CONST 0 (1)
3 LOAD_CONST 1 (2)
6 LOAD_CONST 2 (3)
9 BUILD_LIST 3
12 STORE_NAME 0 (lst) 2 15 LOAD_CONST 0 (1)
18 LOAD_NAME 0 (lst)
21 COMPARE_OP 6 (in)
24 JUMP_IF_FALSE 9 (to 36)
27 POP_TOP 3 28 LOAD_CONST 3 ('Found')
31 PRINT_ITEM
32 PRINT_NEWLINE
33 JUMP_FORWARD 1 (to 37)
>> 36 POP_TOP
>> 37 LOAD_CONST 4 (None)
40 RETURN_VALUE
我们用dis模块解释了demo2.py的字节码,会发现in操作符也会被解释为COMPARE_OP指令,而指令的参数为PyCmp_IN。所以,cmp_outcome实际上主要是处理这些广义上的比较操作,甚至还包括is操作符的实现。对于PyCmp_IN操作,cmp_outcome会委托给PySequence_Contains来判断在序列对象w中是否存在对象v,而对于通常意义上的两个对象之间的大小关系的比较操作,cmp_outcome委托给PyObject_RichCompare进行
object.c
#define RICHCOMPARE(t) (PyType_HasFeature((t), Py_TPFLAGS_HAVE_RICHCOMPARE) \
? (t)->tp_richcompare : NULL) PyObject *
PyObject_RichCompare(PyObject *v, PyObject *w, int op)
{
PyObject *res; assert(Py_LT <= op && op <= Py_GE);
if (Py_EnterRecursiveCall(" in cmp"))
return NULL; /* If the types are equal, and not old-style instances, try to
get out cheap (don't bother with coercions etc.). */
if (v->ob_type == w->ob_type && !PyInstance_Check(v)) {
cmpfunc fcmp;
richcmpfunc frich = RICHCOMPARE(v->ob_type);
/* If the type has richcmp, try it first. try_rich_compare
tries it two-sided, which is not needed since we've a
single type only. */
if (frich != NULL) {
res = (*frich)(v, w, op);
if (res != Py_NotImplemented)
goto Done;
Py_DECREF(res);
}
/* No richcmp, or this particular richmp not implemented.
Try 3-way cmp. */
fcmp = v->ob_type->tp_compare;
if (fcmp != NULL) {
int c = (*fcmp)(v, w);
c = adjust_tp_compare(c);
if (c == -2) {
res = NULL;
goto Done;
}
res = convert_3way_to_object(op, c);
goto Done;
}
} /* Fast path not taken, or couldn't deliver a useful result. */
res = do_richcmp(v, w, op);
Done:
Py_LeaveRecursiveCall();
return res;
}
在PyObject_RichCompare中,首先会确保执行的比较操作介于Py_LT和Py_GE之间,即常规意义上的比较操作。如果进行比较操作的两个对象类型相同,且这两个对象不是用户自定义的类的实例对象,那么首先会选择对象对应的PyTypeObject对象中所定义的tp_richcompare操作。在Python中,无论是Python内建对象,还是用户自定义的类的实例对象,其比较操作都是在各自对应的类型对象中的tp_richcompare或tp_compare中定义的。如果这两个操作都没有成功,Python还会调用do_richcmp进行比较
比较操作的结果——Python中的bool对象
在很多编程语言中,比较操作的结果通常会是一个bool值,即使在没有内建bool值的C中,也会使用0和1来代替bool值Python虚拟机中也有这样两个对立而统一类型的代表成功或失败的PyObject对象:Py_True和Py_False。注意,这两个对象确实是PyObject对象
boolobject.h
/* Don't use these directly */
PyAPI_DATA(PyIntObject) _Py_ZeroStruct, _Py_TrueStruct; /* Use these macros */
#define Py_False ((PyObject *) &_Py_ZeroStruct)
#define Py_True ((PyObject *) &_Py_TrueStruct)
和C语言所采用的策略类似,Python也是利用两个PyIntObject对象来充当bool对象
boolobject.c
/* The type object for bool. Note that this cannot be subclassed! */ PyTypeObject PyBool_Type = {
PyObject_HEAD_INIT(&PyType_Type)
0,
"bool",
sizeof(PyIntObject),
0,
0, /* tp_dealloc */
(printfunc)bool_print, /* tp_print */
0, /* tp_getattr */
0, /* tp_setattr */
0, /* tp_compare */
(reprfunc)bool_repr, /* tp_repr */
&bool_as_number, /* tp_as_number */
0, /* tp_as_sequence */
0, /* tp_as_mapping */
0, /* tp_hash */
0, /* tp_call */
(reprfunc)bool_repr, /* tp_str */
0, /* tp_getattro */
0, /* tp_setattro */
0, /* tp_as_buffer */
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */
bool_doc, /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
0, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&PyInt_Type, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
0, /* tp_init */
0, /* tp_alloc */
bool_new, /* tp_new */
}; /* The objects representing bool values False and True */ /* Named Zero for link-level compatibility */
PyIntObject _Py_ZeroStruct = {
PyObject_HEAD_INIT(&PyBool_Type)
0
}; PyIntObject _Py_TrueStruct = {
PyObject_HEAD_INIT(&PyBool_Type)
1
};
现在,让我们回到demo.py文件和COMPARE_OP,看看demo.py从if a > 10开始执行,到COMPARE_OP指令完成时这段时间内运行时栈的变化:
图1-1 比较操作过程中运行时栈的变化
指令跳跃
在demo.py中,如果第一个判断a > 10成立,那么会立即执行接下来的print语句,如果判断不成立,则跳过print语句,执行下一个判断a <= -2。所以,这里有一个指令跳跃的动作。Python虚拟机中的字节码指令跳跃是如何实现的呢?答案就在COMPARE_OP指令的实现中最后的那个PREDICT宏
ceval.c
#define PREDICT(op) if (*next_instr == op) goto PRED_##op #define PREDICTED(op) PRED_##op: next_instr++ #define PREDICTED_WITH_ARG(op) PRED_##op: oparg = PEEKARG(); next_instr += 3 #define PEEKARG() ((next_instr[2]<<8) + next_instr[1])
在Python中,有些字节码指令通常是成对出现的,更准确说是顺序出现的,这就为上一个字节码指令直接预测下一个字节码指令提供了可能。比如COMPARE_OP的后面就为根据上一个字节码指令直接预测下一个字节码指令提供了可能。COMPARE_OP的后面通常紧跟着JUMP_IF_FALSE或者JUMP_IF_TRUE,这一点我们在demo.py所编译后的字节码可以清楚的看到,而且它们的后面还会跟着一个POP_TOP指令
Python虚拟机中提供了这样的字节码指令预测功能,如果预测成功,就会省去很多无谓的操作,使执行效率得到提升。尤其是当这种字节码指令之间的搭配关系出现的概率非常高时,效率的提升尤为明显
我们可以看到PREDICT(JUMP_IF_FALSE)实际就是直接检查下一条待处理的字节码是否是JUMP_IF_FALSE。如果是,则程序流程会跳转到PRED_JUMP_IF_FALSE标识符对应的代码处,将COMPARE_OP的实现中的PREDICT宏展开,我们就可以看的更清楚了:
ceval.c
if (*next_instr == JUMP_IF_FALSE)
goto PRED_JUMP_IF_FALSE;
if (*next_instr == JUMP_IF_TRUE)
goto PRED_JUMP_IF_TRUE;
那么PRED_JUMP_IF_FALSE和PRED_JUMP_IF_TRUE这些标识符在何处呢?我们知道指令跳跃的目的是为了绕过一些无谓的操作,直接进入JUMP_IF_FALSE或JUMP_IF_TRUE的指令代码,那么显然,这些宏应该位于JUMP_IF_FALSE指令或JUMP_IF_TRUE指令对应的case语句之前
在demo.py中,if a > 10这条判断编译后的字节码序列中,存在JUMP_IF_FALSE指令,那么在COMPARE_OP指令的实现代码的最后,将执行goto PRED_JUMP_IF_FALSE。所以在这里,我们来看一下PRED_JUMP_IF_FALSE标识符是如何被放置到JUMP_IF_FALSE指令代码之前的
ceval.c
PREDICTED_WITH_ARG(JUMP_IF_TRUE);
case JUMP_IF_TRUE:
//[1]:取出之前的比较操作的结果
w = TOP();
//[2]:如果操作结果为False,进行指令跳跃
if (w == Py_False) {
PREDICT(POP_TOP);
goto fast_next_opcode;
}
//[3]:比较操作结果为True
if (w == Py_True) {
JUMPBY(oparg);
goto fast_next_opcode;
}
err = PyObject_IsTrue(w);
if (err > 0) {
err = 0;
JUMPBY(oparg);
}
else if (err == 0)
;
else
break;
continue;
我们再看来看PREDICTED_WITH_ARG宏,我们将它展开
PRED_JUMP_IF_TRUE:
//取指令的参数
oparg = ((next_instr[2]<<8) + next_instr[1]);
//调整next_instr
next_instr += 3;
case JUMP_IF_TRUE:
……
当程序的执行流程跳转到PRED_JUMP_IF_FALSE处时,首先会通过PEEKARG()从字节码指令序列code取出JUMP_IF_FALSE指令的指令参数,这个指令参数指示了当某个条件满足时Python虚拟机会向前跳跃的字节码指令数。在PEEKARG()之后,Python将字节码指针向前移动了3个字节的长度。
仔细想一下,在COMPARE_OP指令的实现中,PREDICT(JUMP_IF_FALSE)处只是判断下一条字节码是否是JUMP_IF_FALSE,并没有移动next_instr。而接下来的PEEKARG()中,我们获得了JUMP_IF_FALSE的指令参数,也没有移动next_instr,所以这时当确认应该执行JUMP_IF_FALSE时,我们必须将字节码指针移动到JUMP_IF_FALSE之后的下一条字节码,因为这时我们已经开始处理JUMP_IF_FALSE了,而next_instr的使命是指出下一条字节码指令时什么。一个字节码长度是一个字节,而字节码参数都是2个字节的,所以这里需要将next_instr向前移动3个字节
根据前面的分析,执行完COMPARE_OP这条指令后,运行时栈存着的是一个Py_False对象,因此在JUMP_IF_FALSE指令中,要进行跳跃动作:
ceval.c
#define JUMPBY(x) (next_instr += (x))
跳跃的距离就是JUMP_IF_FALSE的指令参数,在这里是9,JUMP_IF_FALSE那一行形象的给出,该指令的结果是跳转到偏移位置27,我们看了下,偏移位置27的指令时POP_TOP指令
…………
2 6 LOAD_NAME 0 (a)
9 LOAD_CONST 1 (10)
12 COMPARE_OP 4 (>)
15 JUMP_IF_FALSE 9 (to 27)
18 POP_TOP 3 19 LOAD_CONST 2 ('a>10')
22 PRINT_ITEM
23 PRINT_NEWLINE
24 JUMP_FORWARD 72 (to 99)
>> 27 POP_TOP 4 28 LOAD_NAME 0 (a)
31 LOAD_CONST 3 (-2)
34 COMPARE_OP 1 (<=)
37 JUMP_IF_FALSE 9 (to 49)
40 POP_TOP
…………
你可能会疑惑,为什么JUMP_IF_FALSE的参数是9,然后跳转到偏移27的位置,这个27是如何确定的?很简单,我们现在所处的JUMP_IF_FALSE是偏移15,这条指令已经执行完毕了,所以这个跳转参数9是基于下一条指令的偏移位置,下一条的偏移位置是18,所以18+9=27,应该跳到偏移位置27的位置
但为什么是跳转到27 POP_TOP这条指令,而不是偏移位置28的LOAD_NAME指令呢?毕竟第一个if语句比较失败了,那么我们就应该立马开始比较第二个if语句呀!别忘了,运行时栈中还保留着上一次比较结果Py_False,如果我们直接进行下一次比较,而不执行POP_TOP将运行时栈栈顶的Py_False弹出,那么运行时栈会慢慢被比较结果给占满,因此,先执行POP_TOP,再进行再一次比较,是再合理不过的事情了
如果JUMP_IF_FALSE执行时从运行时栈得到的是一个Py_True对象,意味着a > 10成立,接下来就要执行print语句。再次利用PREDICT宏,对POP_TOP指令进行预测,快速进行运行时栈的清理工作,并为print的执行做准备
ceval.c
case POP_TOP:
v = POP();
Py_DECREF(v);
goto fast_next_opcode;
可能你已经注意到,这里有两个供于预测的宏:PREDICTED_WITH_ARG、PREDICT,正如它们名字所表达的意思,PREDICTED_WITH_ARG是处理有参指令,PREDICT是处理无参指令,POP_TOP正是一条无参指令。实际上,PREDICTED_WITH_ARG和PREDICT目的一致,都是调整next_instr,使其指向下一条待执行的字节码指令
值的注意的是,在执行JUMP_IF_FALSE时,无论运行时栈的比较结果是Py_False,还是Py_True,都将转到fast_next_opcode,执行下一条字节码指令,而不会再执行完JUMP_IF_FALSE之后,如函数调用那般返回COMPARE_OP,再判断下一条操作是否是PREDICT(JUMP_IF_TRUE)
在整个指令跳跃的过程中,出现了两次跳跃,第一次是通过PREDICT(JUMP_IF_FALSE)中的goto语句进行跳跃,这次跳跃影响的是Python虚拟机自身,即实现Python的C代码。而在JUMP_IF_FALSE的指令代码中通过JUMP完成的跳跃是在Python应用程序层面的跳跃,影响的是Python应用程序
在COMPARE_OP中,有一个PREDICT(JUMP_IF_TRUE),但在我们编译后的字节码指令中一直没有JUMP_IF_TRUE这条指令。那么,这条指令会在什么时候出现呢?当我们把if a > 10替换成if not a > 10,Python编译器才会为if语句编译出JUMP_IF_TRUE这条指令
最后还有一点要指出的是,在print的执行中,同样会出现指令跳跃的动作。因为从程序上来看,只要符合一个if的条件,那么后续的if也没有再判断的必要了。所以Python虚拟机的执行流程会一跃千里,直接跳转到if控制结构的末尾的第一条字节码指令,这个跳跃由JUMP_FORWARD完成
case JUMP_FORWARD:
JUMPBY(oparg);
goto fast_next_opcode;
跳跃的距离是当前字节码指令与if控制结构之后的第一条字节码指令(demo.py中倒数第二条指令"99 LOAD_CONST 8")之间的距离,包括所有字节码指令和其对应的指令参数。所以我们看到,在不同的print语句编译后的指令序列中,JUMP_FORWARD的指令参数是不同的,但它们跳跃的目标却是一致的,都是"99 LOAD_CONST 8"
从这里也可以看到,在JUMP_FORWARD后面的那条POP_TOP对于print语句没有任何意义,它实际上就是为了JUMP_IF_FALSE或JUMP_IF_TRUE而生
Python虚拟机之if控制流(一)的更多相关文章
- Python虚拟机之异常控制流(五)
Python中的异常控制语义结构 在Python虚拟机之异常控制流(四)这一章中,我们考察了Python的异常在虚拟机中的级别上是什么东西,抛出异常这个动作在虚拟机的级别上对应的行为,最后,我们还剖析 ...
- Python虚拟机之异常控制流(四)
Python虚拟机中的异常控制流 先前,我们分别介绍了Python虚拟机之if控制流(一).Python虚拟机之for循环控制流(二)和Python虚拟机之while循环控制结构(三).这一章,我们来 ...
- 《python解释器源码剖析》第11章--python虚拟机中的控制流
11.0 序 在上一章中,我们剖析了python虚拟机中的一般表达式的实现.在剖析一遍表达式是我们的流程都是从上往下顺序执行的,在执行的过程中没有任何变化.但是显然这是不够的,因为怎么能没有流程控制呢 ...
- Python虚拟机之for循环控制流(二)
Python虚拟机中的for循环控制流 在Python虚拟机之if控制流(一)这一章中,我们了解if控制流的字节码实现,在if控制结构中,虽然Python虚拟机会在不同的分支摇摆,但大体还是向前执行, ...
- Python虚拟机之while循环控制结构(三)
Python虚拟机中的while循环控制结构 在Python虚拟机之if控制流(一)和Python虚拟机之for循环控制流(二)两个章节中,我们介绍了if和for两个控制结构在Python虚拟机中的实 ...
- Python虚拟机中的一般表达式(一)
在Python虚拟机框架这一章中,我们通过PyEval_EvalFrameEx看到了Python虚拟机的整体框架.而这章开始,我们将了解Python虚拟机是如何完成对Python的一般表达式的执行,这 ...
- 《python解释器源码剖析》第10章--python虚拟机中的一般表达式
10.0 序 上一章中,我们通过PyEval_EvalFrameEx看到了python虚拟机的整体框架,那么这一章我们将深入到PyEval_EvalFrameEx的各个细节当中,深入剖析python的 ...
- python虚拟机中的异常流控制
异常:对程序运行中的非正常情况进行抽象.并且提供相应的语法结构和语义元素,使得程序员能够通过这些语法结构和语义元素来方便地描述异常发生时的行为. 1.Python中的异常机制: 1.1Python虚拟 ...
- 虚拟机:Python虚拟机的基本了解
探索某个东西,我们需要知道这个东西是用来干什么的,能给我们带来什么,解决了什么样的问题,有什么优缺点等等:简要了解了一下Python虚拟机的特征: 目前有几个疑问: 1.对象 · Python通过对象 ...
随机推荐
- webpack分开打包和合并打包的瘦身
webpack.config.js 记录一下优化webpack的几个点: 1. devtool: false, //产品阶段不应该有devtool entry: { bundle : pa ...
- WebStorm快捷键(Mac版)
编辑 Command+alt+T 用 (if..else, try..catch, for, etc.)包住 Command+/ 注释/取消注释的行注释 Command+alt+/ 注释/取消注释与块 ...
- android pm命令
把网络apk下载到盒子或者其他安卓设备上 1.adb push windows的原路径 android设备的路径 2.pm install android设备的路径 注意:这里pm命令是安卓设备才有的 ...
- Kendo UI 单页面应用(三) View
Kendo UI 单页面应用(三) View view 为屏幕上某个可视部分,可以处理用户事件. View 可以通过 HTML 创建或是通过 script 元素.缺省情况下 View 将其所包含的内容 ...
- mui自定义事件实例
监听自定义事件(接收页面应用) 添加自定义事件监听操作和标准js事件监听类似,可直接通过window对象添加,如下: window.addEventListener('customEvent',fun ...
- java 通过文件后缀名查找文件
最近开发项目的时候需要过滤出一些指定的文件,所以有了以下的一些代码: /** **该类主要是过滤得到指定后缀名的文件 **/ public class DataFileFilter implement ...
- linux命令之文本查看
vi掌握练习: 英文文档,相同的单词复制粘贴光标移动编辑等操作: cat:显示文件所有内容,小文件查看时使用. 缺点:文件大时不方便查看,文件很大时,会抢占系统资源,会出现命令崩溃. [zyj@loc ...
- svn与git区别简介,git分支操作在mac客户端soureTree和使用命令行如何实现
svn与git区别简介: 性能方面(经过实践的) svn:下载速度慢,因为它其中的源文件太多,并且在show log日志的时候每次都需要去服务器拉取,速度很慢 git:下载速度快,并且git clon ...
- vmware 虚机NAT模式,局域网可访问
本地VMware虚拟机,网络模式为NAT,现在需要局域网其他电脑通过ssh连接这台VMware虚拟机 宿主机地址:192.168.3.26 VMware虚拟机地址:192.168.239.137 局域 ...
- javaSe-反射2
//创建实体类 package com.java.chap07.sec03; public class Student { private String name; private Integer a ...