Python 2.7 源码 - 简单对象创建的字节码分析

栏目: Python · 发布时间: 6年前

内容简介:Python 2.7 源码 - 简单对象创建的字节码分析

Python 源码编译后,有常量表,符号表。一个作用域运行时会对应一个运行时栈。

大部分字节码就是基于常量表、符号表和运行时栈,运算后得到所需结果。

本篇就来分析简单对象创建的字节码。以下面这段代码为分析样本:

i = 1
s = 'python'
d = {}
l = []

对生成的 pyc 文件解析,可得如下的结构,其中包括字节码反编译的结果:

magic 03f30d0a
moddate 836a595a (Sat Jan 13 10:10:11 2018)
<code>
   <argcount> 0 </argcount>
   <nlocals> 0</nlocals>
   <stacksize> 1</stacksize>
   <flags> 0040</flags>
   <codeobject> 6400005a00006401005a01006900005a02006700005a030064020053</codeobject>
   <dis>
  1           0 LOAD_CONST               0 (1)
              3 STORE_NAME               0 (i)

  2           6 LOAD_CONST               1 ('python')
              9 STORE_NAME               1 (s)

  3          12 BUILD_MAP                0
             15 STORE_NAME               2 (d)

  4          18 BUILD_LIST               0
             21 STORE_NAME               3 (l)
             24 LOAD_CONST               2 (None)
             27 RETURN_VALUE
   </dis>
   <names> ('i', 's', 'd', 'l')</names>
   <varnames> ()</varnames>
   <freevars> ()</freevars>
   <cellvars> ()</cellvars>
   <filename> '.\\test.py'</filename>
   <name> '<module>'</name>
   <firstlineno> 1</firstlineno>
   <consts>
      1
      'python'
      None
   </consts>
   <lnotab> 060106010601</lnotab>
</code>

我们清楚的看到 consts 常量表, names 符号表,这些表中的元素都是有明确顺序的。

整数赋值

第一条语句 i = 1 。对应的字节码为:

0 LOAD_CONST               0 (1)
3 STORE_NAME               0 (i)

LOAD_CONST 对应的 C 语言源码为:

TARGET(LOAD_CONST)
{
    x = GETITEM(consts, oparg); // 从常量表 oparg 位置处取出对象
    Py_INCREF(x);
    PUSH(x); // 压入堆栈
    FAST_DISPATCH();
}

该字节码带参,这里参数为 0。表示从常量表第 0 个位置取出整数,并将该数压入运行时栈:

+-------+----------+
| stack | f_locals |
+-------+----------+
| 1     |          |
|       |          |
|       |          |
+-------+----------+

左侧为运行时栈,右侧为当前作用域内的局部变量。

STORE_NAME 所对应的 C 语言源码为:

TARGET(STORE_NAME)
{
    w = GETITEM(names, oparg); // 从符号表 oparg 位置处取出符号名
    v = POP(); // 弹出运行时栈的栈顶元素
    if ((x = f->f_locals) != NULL) {
        if (PyDict_CheckExact(x))
            err = PyDict_SetItem(x, w, v); // 将符号名作为键,栈顶元素作为值,放入字典中
        else
            err = PyObject_SetItem(x, w, v);
        Py_DECREF(v);
        if (err == 0) DISPATCH();
        break;
    }
    t = PyObject_Repr(w);
    if (t == NULL)
        break;
    PyErr_Format(PyExc_SystemError,
                    "no locals found when storing %s",
                    PyString_AS_STRING(t));
    Py_DECREF(t);
    break;
}

该字节码带参,参数为 0。表示从符号表第 0 个位置处取出符号名,即 i 。然后弹出运行时栈的栈顶元素,并将符号名作为键,栈顶元素作为值,放入字典中 f_locals

+-------+------------+
| stack | f_locals   |
+-------+------------+
|       | i, <int 1> |
|       |            |
|       |            |
+-------+------------+

字符串赋值

语句 s = 'python' 所对应的字节码为:

6 LOAD_CONST               1 ('python')
9 STORE_NAME               1 (s)

和整数赋值的字节码完全相同,只是参数不同。这里不再做重复分析,赋值后,运行时栈变为:

+-------+-------------------+
| stack | f_locals          |
+-------+-------------------+
|       | i, <int 1>        |
|       | s, <str 'python'> |
|       |                   |
+-------+-------------------+

字典赋值

语句 d = {} 对应的字节码为:

12 BUILD_MAP                0
15 STORE_NAME               2 (d)

BUILD_MAP 所对应的 C 语言源码为:

// ceval.c
TARGET(BUILD_MAP)
{
    x = _PyDict_NewPresized((Py_ssize_t)oparg);
    PUSH(x);
    if (x != NULL) DISPATCH();
    break;
}

// dictobject.c
PyObject *
_PyDict_NewPresized(Py_ssize_t minused)
{
    PyObject *op = PyDict_New();

    if (minused>5 && op != NULL && dictresize((PyDictObject *)op, minused) == -1) {
        Py_DECREF(op);
        return NULL;
    }
    return op;
}

该字节码带参,参数为 0。而深入 _PyDict_NewPresized 可以看到,若参数小于 5,实际上创建的是默认大小的字典。创建完毕后,会将该字典对象压入运行时栈。

+--------+-------------------+
| stack  | f_locals          |
+--------+-------------------+
| <dict> | i, <int 1>        |
|        | s, <str 'python'> |
|        |                   |
+--------+-------------------+

最后 STORE_NAME 将该对象与符号 d 绑定:

+-------+-------------------+
| stack | f_locals          |
+-------+-------------------+
|       | i, <int 1>        |
|       | s, <str 'python'> |
|       | d, <dict>         |
+-------+-------------------+

列表赋值

语句 l = [] 对应的字节码为:

18 BUILD_LIST               0
21 STORE_NAME               3 (l)

BUILD_LIST 对应的 C 语言源码为:

TARGET(BUILD_LIST)
{
    x =  PyList_New(oparg); // 创建空列表
    if (x != NULL) {
        for (; --oparg >= 0;) {
            w = POP(); // 从栈中弹出元素
            PyList_SET_ITEM(x, oparg, w); // 将弹出的元素放入列表中
        }
        PUSH(x); // 将列表对象放入栈中
        DISPATCH();
    }
    break;
}

该字节码首先创建一个列表,列表依据参数值预先分配空间。这里不对列表做深入分析,只指出,这里的空间大小不是存放元素所占用的空间,而是 PyObject * 指针。

列表建完后,便会不停从运行时栈中弹出元素,然后将元素放入列表中。这里是空列表,所以 BUILD_LIST 运行时,栈为空,该字节码的参数也为 0。

我们换一个非空列表来看一下:

l = [1, 2, 3]

编译后

1           0 LOAD_CONST               0 (1)
            3 LOAD_CONST               1 (2)
            6 LOAD_CONST               2 (3)
            9 BUILD_LIST               3
           12 STORE_NAME               0 (l)
           15 LOAD_CONST               3 (None)
           18 RETURN_VALUE

可以看到,在 BUILD_LIST 之前会将三个对象压入运行时栈中。

回到本文最初的 Python 程序,4 条语句运行完后, f_locals 为:

+-------+-------------------+
| stack | f_locals          |
+-------+-------------------+
|       | i, <int 1>        |
|       | s, <str 'python'> |
|       | d, <dict>         |
|       | l, <list>         |
+-------+-------------------+

结束

在最后,我们还看到两行字节码:

24 LOAD_CONST               2 (None)
27 RETURN_VALUE

它们好像与我们的四条赋值语句没有任何关系。原来,Python 在执行了一段 CodeBlock 后,一定要返回一些值,既然如此,那就随便返回一个 None 好了。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

The Little MLer

The Little MLer

Matthias Felleisen、Daniel P. Friedman、Duane Bibby、Robin Milner / The MIT Press / 1998-2-19 / USD 34.00

The book, written in the style of The Little Schemer, introduces instructors, students, and practicioners to type-directed functional programming. It covers basic types, quickly moves into datatypes, ......一起来看看 《The Little MLer》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具