美文网首页
五、PyCodeObject与Python程序执行

五、PyCodeObject与Python程序执行

作者: cocosysu | 来源:发表于2016-10-25 16:41 被阅读823次

    一、Python程序执行原理

    1.一个小程序

    # [demo.py]
    class A(object):
      pass
    
    def func():
      a = 5
      b = 2
      print 'hello coco!'
    
    a = A()
    func()
    

    对于如上一个简单程序,稍有python编程经验都能理轻松理解。执行指令:

    python demo.py
    

    如我们预期,程序会产生执行结果:

    hello coco!
    

    2.执行流程

    如上所示,一个文本文件demo.py,经过python施加魔法后变成机器指令并执行起来。那么python内部究竟是如何运作呢?如我们所知,python是一门解释性语言,它的执行流程与Java、C#这些解释型语言一样可以用两个词概括编译、解析。

    1)编译

    对于上述的python程序,执行后细心的同学会发现程序文件夹多了一个demo.pyc文件。事实上,这个pyc文件就是对demo.py文件的编译结果。编译结果是一个称之为python字节码序列(为运行时Python虚拟机所执行的指令)。python字节码与机器指令码很相似:

    12 MAKE_FUNCTION            0
    15 CALL_FUNCTION            0
    18 BUILD_CLASS         
    19 STORE_NAME   
    

    先把python文件编译成字节码主要有两个好处,第一方面也是最重要的:跨平台是python的一大特性,不同机器的机器指令是不同的,将代码先编译为python解释器识别的字节码,运行时可以根据不同机器执行相应的机器指令;第二方面,将python先编译成字节码可以提升运行性能,将编译内容事先存储到pyc文件中(pyc文件中不单存储了字节码信息),如无修改运行时无需再次编译。

    2)解释

    python解释器首先将python文件编译为字节码,解释为字节码后python的解析器-python虚拟机就开始接手所有的工作:依次读入每条字节码指令并逐条执行。

    二、PyCodeObject

    如上我们大概了解了python的执行流程以及python字节码的概念,接下来将深入源码探索python实现这些机制的内部细节。首先看python的编译过程,如前说到python将编译结果字节码存储到pyc文件中,事实上这个结论还不够全面。pyc中除了字节码还存储了很多python程序运行时信息包括定义的字符串、常量等。python的编译结果的的奥秘全部藏在PyCodeObject中,PyCodeObject是python中的一个命名空间(命名空间指的是有独立变量定义的Code block如函数、类、模块等)的编译结果在内存中的表示。

    /* code.h */
    /* Bytecode object */
    typedef struct {
        PyObject_HEAD
        int co_argcount;        /* #arguments, except *args */
        int co_nlocals;     /* #local variables */
        int co_stacksize;       /* #entries needed for evaluation stack */
        int co_flags;       /* CO_..., see below */
        PyObject *co_code;      /* instruction opcodes */
        PyObject *co_consts;    /* list (constants used) */
        PyObject *co_names;     /* list of strings (names used) */
        PyObject *co_varnames;  /* tuple of strings (local variable names) */
        PyObject *co_freevars;  /* tuple of strings (free variable names) */
        PyObject *co_cellvars;      /* tuple of strings (cell variable names) */
        /* The rest doesn't count for hash/cmp */
        PyObject *co_filename;  /* string (where it was loaded from) */
        PyObject *co_name;      /* string (name, for reference) */
        int co_firstlineno;     /* first source line number */
        PyObject *co_lnotab;    /* string (encoding addr<->lineno mapping) See Objects/lnotab_notes.txt for details. */
        void *co_zombieframe;     /* for optimization only (see frameobject.c) */
        PyObject *co_weakreflist;   /* to support weakrefs to code objects */
    } PyCodeObject;
    

    从源码中可以看到PyCodeObject中包含了co_argcount和co_nlocals等字段,这些字段的内容如下表:

    • co_nlocals : Code Block中局部变量个数,包括其位置参数个数
    • co_stacksize : 执行该段Code Block需要的栈空间
    • co_code : Code Block编译得到的字节码指令序列,以PyStringObject的形式存在
    • co_consts: PyTupleObject,保存Code Block中的所有常量
    • co_names: PyTupleObject, 保存Code Block中的所有符号
    • co_varnames: Code Block中的局部变量名集合
    • co_freevars : Python实现闭包存储内容
    • co_cellvars : Code Block中内部嵌套函数所引用的局部变量名集合
    • co_filename : Code Block对应的.py文件的完整路径
    • co_name : Code Block的名字,通常是函数名或类名

    python层的code对象与PyCodeObject对应,通过python的compile接口可以查看code对象(即PyCodeObject对象)。

    >>> source = open('demo.py').read()
    >>> co = compile(source,'demo.py', 'exec')
    >>> type(co)
    <type 'code'>
    >>> dir(co)
    ['__class__', '__cmp__', '__delattr__', '__doc__', '__eq__', '__format__', '__ge__', 
    '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', 
    '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
    '__str__', '__subclasshook__', 'co_argcount', 'co_cellvars', 'co_code', 'co_consts', 
    'co_filename', 'co_firstlineno', 'co_flags', 'co_freevars', 'co_lnotab', 'co_name', 
    'co_names', 'co_nlocals', 'co_stacksize', 'co_varnames']
    >>> co.co_names
    ('object', 'A', 'func', 'a')
    >>> co.co_filename
    'demo.py'
    

    三、持久化PyCodeObject

    PyCodeObject中不仅存储了代码对应的字节码指令序列,还保存了代码的运行时信息。PyCodeObject是这些信息在内存中的表示,为以后执行能重复利用这些编译信息,减少编译时间(在代码未改变的情况下),python解释器会把这些信息序列化到pyc文件中。

    /*import.c*/
    static void write_compiled_module(PyCodeObject *co, char *cpathname, struct stat *srcstat, time_t mtime)
    {
        PyMarshal_WriteLongToFile(pyc_magic, fp, Py_MARSHAL_VERSION);
        /* First write a 0 for mtime */
        PyMarshal_WriteLongToFile(0L, fp, Py_MARSHAL_VERSION);
        PyMarshal_WriteObjectToFile((PyObject *)co, fp, Py_MARSHAL_VERSION);
    }
    

    pyc文件中会记录把编译的版本号,编译时间、PyCodeObject等信息序列化到pyc文件中,实际的序列化过程在marshal.c中实现:

    /*marshal.c*/
    void PyMarshal_WriteObjectToFile(PyObject *x, FILE *fp, int version)
    {
        WFILE wf;
        wf.fp = fp;
        wf.error = WFERR_OK;
        wf.depth = 0;
        wf.strings = (version > 0) ? PyDict_New() : NULL;
        wf.version = version;
        w_object(x, &wf);
        Py_XDECREF(wf.strings);
    }
    
    static void w_object(PyObject *v, WFILE *p)
    {
        Py_ssize_t i, n;
    
        p->depth++;
    
        if (p->depth > MAX_MARSHAL_STACK_DEPTH) {
            p->error = WFERR_NESTEDTOODEEP;
        }
        else if (v == NULL) {
            w_byte(TYPE_NULL, p);
        }
        else if ...
    

    四、Python字节码

    在opcode.h中定义了python的字节码指令定义。

    /* opcode.h*/
    /* Instruction opcodes for compiled code */
    #define LOAD_CONST  100 /* Index in const list */
    #define LOAD_NAME   101 /* Index in name list */
    #define BUILD_TUPLE 102 /* Number of tuple items */
    #define BUILD_LIST  103 /* Number of list items */
    #define BUILD_SET   104     /* Number of set items */
    #define BUILD_MAP   105 /* Always zero for now */
    #define LOAD_ATTR   106 /* Index in name list */
    

    python中也提供了dis工具可以查看PyCodeObject对应的字节码指令:

    >>> source = open('demo.py').read()
    >>> co = compile(source, 'demo.py', 'exec')
    >>> import dis
    >>> dis.dis(co)
      1           0 LOAD_CONST               0 ('A')
                  3 LOAD_NAME                0 (object)
                  6 BUILD_TUPLE              1
                  9 LOAD_CONST               1 (<code object A at 0x7f993a73adb0, file "demo.py", line 1>)
                 12 MAKE_FUNCTION            0
                 15 CALL_FUNCTION            0
                 18 BUILD_CLASS         
                 19 STORE_NAME               1 (A)
    
      4          22 LOAD_CONST               2 (<code object func at 0x7f993a662a30, file "demo.py", line 4>)
                 25 MAKE_FUNCTION            0
                 28 STORE_NAME               2 (func)
    
      9          31 LOAD_NAME                1 (A)
                 34 CALL_FUNCTION            0
                 37 STORE_NAME               3 (a)
    
     10          40 LOAD_NAME                2 (func)
                 43 CALL_FUNCTION            0
                 46 POP_TOP             
                 47 LOAD_CONST               3 (None)
                 50 RETURN_VALUE  
    

    相关文章

      网友评论

          本文标题:五、PyCodeObject与Python程序执行

          本文链接:https://www.haomeiwen.com/subject/aapruttx.html