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

还是从字节码开始分析

a = 1
b = 2
c = 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_ADD
14 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行代码的惊讶,再到最后的释然,这就是源码分析时的快感,让源码调动起自己的情绪,随着代码去旅行,可能就是最好的分析方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值