我们已经知道,python是被编译为字节码的形式运行的,比如23的字节表示BINARY_ADD指令(参考opcode.h)。

在Python中,一切都是基于PyObject派生,由Python代码编译生成的字节码也不例外,都存储在PyCodeObject的对象中。

上一节我们贴出了示例代码:

# File : func.py
x = 10
def foo(x):
  def bar(x):
    y = x-1
    return y
  return bar(y)

z = foo(x)

查看这段代码的汇编

这里用的是python3,Python2得到的汇编码会略有不同,但分析源码的方法和思路是一样的。

python -m dis func.py

会得到如下的汇编吗

  1           0 LOAD_CONST               0 (10)
              2 STORE_NAME               0 (x)

  2           4 LOAD_CONST               1 (<code object foo at 0x1103334b0, file "func.py", line 2>)
              6 LOAD_CONST               2 ('foo')
              8 MAKE_FUNCTION            0
             10 STORE_NAME               1 (foo)

  8          12 LOAD_NAME                1 (foo)
             14 LOAD_NAME                0 (x)
             16 CALL_FUNCTION            1
             18 STORE_NAME               2 (z)
             20 LOAD_CONST               3 (None)
             22 RETURN_VALUE

Disassembly of <code object foo at 0x1103334b0, file "func.py", line 2>:
  3           0 LOAD_CONST               1 (<code object bar at 0x110333030, file "func.py", line 3>)
              2 LOAD_CONST               2 ('foo.<locals>.bar')
              4 MAKE_FUNCTION            0
              6 STORE_FAST               1 (bar)

  6           8 LOAD_FAST                1 (bar)
             10 LOAD_GLOBAL              0 (y)
             12 CALL_FUNCTION            1
             14 RETURN_VALUE

Disassembly of <code object bar at 0x110333030, file "func.py", line 3>:
  4           0 LOAD_FAST                0 (x)
              2 LOAD_CONST               1 (1)
              4 BINARY_SUBTRACT
              6 STORE_FAST               1 (y)

  5           8 LOAD_FAST                1 (y)
             10 RETURN_VALUE

在汇编offset为4的地方,我们看到了LOAD_CONST,参数是 <code object foo> ,紧随其后是MAKE_FUNCTION 指令和STORE_NAME 指令。

在前几章,我们分析过STORE_NAME是把value stack栈顶的第一个元素(也就是栈顶第一个指针指向的对象),存到变量名的hash表里。从STORE_NAME后面跟随的名字foo和bar,我们可以猜测这个对象就是我们的函数。继续推测,我们的MAKE_FUNCTION 可能往value stack栈里写了什么。

我们完整的看定义函数的这四行汇编指令:

  2           4 LOAD_CONST               1 (<code object foo at 0x1103334b0, file "func.py", line 2>)
              6 LOAD_CONST               2 ('foo')
              8 MAKE_FUNCTION            0
             10 STORE_NAME               1 (foo)

本章我们将着重分析第一行 code object 的作用,MAKE_FUNCTION 指令将在下一节讲。

CodeObject

PyCodeObject定义在 cpython/Include/code.h 中,代码已经包含了丰富的注释信息,可以推测出大致含义。

这里把代码完全贴出来,是为了下面进一步的对比。

/* File : cpython/Include/code.h
/* Bytecode object */
typedef struct {
    PyObject_HEAD
    int co_argcount;            /* #arguments, except *args */
    // ...
    PyObject *co_code;          /* instruction opcodes */
    PyObject *co_consts;        /* list (constants used) */
    PyObject *co_names;         /* list of strings (names used) */
    // ...
} PyCodeObject;

我们将以1+2=3的Python代码,来了解CodeObject内存了哪些东西,但是这次,我们会用compile字符串的方式查看:

code_str = """
a = 1
b = 2
c = a+b
print(c)
"""
co = compile(code_str, 'my_code', 'exec')
dir(co)

通过dir命令,我们可以看到codeobject内部有 co_code, co_names等变量,这和C代码中的成员一致。进一步,我们用dis.dis(co.co_code)查看字节码反汇编出的指令,可以看到完整的指令集,只是指令最后的命名变成了整数,通过co.co_names可以看到代码中需要的变量名,指令的整数就表示co_names中的命名顺序。

举一反三的,我们也可以验证其他codeobject的成员变量是什么。

CodeObject的创建

CodeObject的创建在是由PyCode_New函数完成,具体代码在cpython/Python/codeobject.c 中。

PyCodeObject *
PyCode_New(int argcount, int kwonlyargcount,
           int nlocals, int stacksize, int flags,
           PyObject *code, PyObject *consts, PyObject *names,
           PyObject *varnames, PyObject *freevars, PyObject *cellvars,
           PyObject *filename, PyObject *name, int firstlineno,
           PyObject *lnotab)
{
           // ...
           // 申请codeobject的内存空间
           co = PyObject_NEW(PyCodeObject, &PyCode_Type);
           // ...
           return co;
}

在Python的Compiler编译后,会生成代码对应的字节码(byte)形式,这个字节码对应着PyCode_New函数的入参PyObject *code。构建PyCodeObject过程中,Interpreter首先获取若干入参,做一堆逻辑判断后,向内存申请codeobject的空间,把相关引用的计数加一后,返回codeobject。

results matching ""

    No results matching ""