python3.6 源码分析(二):另一个例子

来源:互联网 发布:origin淘宝买游戏 编辑:程序博客网 时间:2024/06/05 20:45

还是从字节码开始分析

a = 1b = 2c = a + b

编译:

0 LOAD_CONST               0 (1)2 STORE_NAME               0 (a)4 LOAD_CONST               1 (2)6 STORE_NAME               1 (b)8 LOAD_NAME                0 (a)10 LOAD_NAME                1 (b)12 BINARY_ADD14 STORE_NAME               2 (c)

前面两个变量绑定上节已经分析过了,我们从第八个字节开始分析,by the way,python3.6开始字节码变成了固定两个字节。
首先是LOAD_NAME这个字节码,顾名思义,应该是将名字对应的值加载到栈顶,让我们看看是不是这样:

TARGET(LOAD_NAME) {            PyObject *name = GETITEM(names, oparg);            PyObject *locals = f->f_locals;            PyObject *v;            if (locals == NULL) {                PyErr_Format(PyExc_SystemError,                             "no locals when loading %R", name);                goto error;            }            if (PyDict_CheckExact(locals)) {                v = PyDict_GetItem(locals, name);                Py_XINCREF(v);            }            else {                v = PyObject_GetItem(locals, name);                if (v == NULL) {                    if (!PyErr_ExceptionMatches(PyExc_KeyError))                        goto error;                    PyErr_Clear();                }            }            if (v == NULL) {                v = PyDict_GetItem(f->f_globals, name);                Py_XINCREF(v);                if (v == NULL) {                    if (PyDict_CheckExact(f->f_builtins)) {                        v = PyDict_GetItem(f->f_builtins, name);                        if (v == NULL) {                            format_exc_check_arg(                                        PyExc_NameError,                                        NAME_ERROR_MSG, name);                            goto error;                        }                        Py_INCREF(v);                    }                    else {                        v = PyObject_GetItem(f->f_builtins, name);                        if (v == NULL) {                            if (PyErr_ExceptionMatches(PyExc_KeyError))                                format_exc_check_arg(                                            PyExc_NameError,                                            NAME_ERROR_MSG, name);                            goto error;                        }                    }                }            }            PUSH(v);            DISPATCH();        }

足足50行,加载个名字而已,为啥这么麻烦。
第一行GETITEM ,看看宏定义:

#define GETITEM(v, i) PyTuple_GetItem((v), (i))

原来names域是一个tuple,好吧,第一步貌似就已经将名字取到了,接下来还要做啥?当然是取名字对应的值了。

后面的事情简直不能更简单,注意这3行:

v = PyDict_GetItem(locals, name);v = PyDict_GetItem(f->f_globals, name);v = PyDict_GetItem(f->f_builtins, name);

这就是python寻找变量的值的顺序了,依次分别尝试从locals,globals,builtins里面去寻找这个名字对应的值,最后:

PUSH(v);

将找到的值压入栈顶,完了。
至于那些个

Py_INCREF(v);

这些东西,是增加垃圾回收引用计数,对我们的分析没有影响,就不管他了。
还有一个问题:最后一行那个DISPATHC()又是啥?
点进去一看,哈哈,就是个continue。

下一步

接着两个LOAD_NAME的是一个BINARY_ADD,想都不用想,一定是把两个栈顶的值POP出来加起来,然后将结果放回栈顶,让我们验证一下:

       TARGET(BINARY_ADD) {            PyObject *right = POP();            PyObject *left = TOP();            PyObject *sum;            if (PyUnicode_CheckExact(left) &&                     PyUnicode_CheckExact(right)) {                sum = unicode_concatenate(left, right, f, next_instr);                /* unicode_concatenate consumed the ref to left */            }            else {                sum = PyNumber_Add(left, right);                Py_DECREF(left);            }            Py_DECREF(right);            SET_TOP(sum);            if (sum == NULL)                goto error;            DISPATCH();        }

可以说代码是非常的短了,看看做了些什么:

            PyObject *right = POP();            PyObject *left = TOP();            PyObject *sum;

首先是取出两个加数,然后定义了和的指针,注意left是TOP取出来的,说明此时left还在栈顶,然后发生了一些奇怪的事情:

if (PyUnicode_CheckExact(left) &&                     PyUnicode_CheckExact(right)) {                sum = unicode_concatenate(left, right, f, next_instr);            }

居然可以做字符串加法。。还好我们传入的都是数字,所以应该到了else里面:

else {                sum = PyNumber_Add(left, right);                Py_DECREF(left);            }Py_DECREF(right);            SET_TOP(sum);

结果为sum,SET_TOP这个宏直接将栈顶元素指向了sum,此时栈顶就是计算结果了。
还差最后一步,将结果赋值给c,STORE_NAME,我们已经不能更熟悉。。略。

总结一下

从最初的轻视,到发现一个LOAD_NAME就有50行代码的惊讶,再到最后的释然,这就是源码分析时的快感,让源码调动起自己的情绪,随着代码去旅行,可能就是最好的分析方法。