Python PyCodeObject 字节码对象


PyCodeObject && magic number

PyCodeObject

python在执行python文件时,首先执行的动作是编译,编译时python会收集源代码中一切有用的信息,并生成PyCodeObject对象,这个对象在python运行结束时有时还会被保存在pyc文件中。python在对代码进行编译时,会对代码的每个code block生成一个PyCodeObject对象,即对每个命名空间生成一个code block。

struct PyCodeObject {
    PyObject_HEAD
    int co_argcount;           
    int co_posonlyargcount;    
    int co_kwonlyargcount;     
    int co_nlocals;            
    int co_stacksize;           
    int co_flags;              
    int co_firstlineno;        
    PyObject *co_code;          
    PyObject *co_consts;       
    PyObject *co_names;         
    PyObject *co_varnames;      
    PyObject *co_freevars;      
    PyObject *co_cellvars;      
    ...
    ...
    ...
};
  • co_argcount:python函数的位置参数的个数。

      >>> def func(a,b,c):
      ...     pass
      ...
      >>> print(func.__code__.co_argcount)
      3
      >>>
  • co_posonlyargcount:只能通过位置参数传递的参数个数。

      >>> def func(a,b,c,/,d,e):
      ...     pass
      ...
      >>> print(func.__code__.co_posonlyargcount)
      3
      >>>
    
  • co_kwonlyargcount:只能通过关键词传递参数的个数。

      >>> def func(a,b,c,*,d,e):
      ...     pass
      ...
      >>> print(func.__code__.co_kwonlyargcount)
      2
      >>>
  • co_nlocals:代码块中局部变量的个数,也包括参数。

      >>> def func(a,b,c,*,d,e):
      ...     f = 1
      ...     g = 2
      ...
      >>> print(func.__code__.co_nlocals)
      7
      >>>
  • co_stacksize:执行该段代码需要的栈空间。

      >>> def func(num):
      ...     if num == 1:
      ...         return 1
      ...     return num * func(num - 1)
      ...
      >>> print(func.__code__.co_stacksize)
      4
      >>>
  • co_flags:记录当前代码块的标识,生成器的 flags 特征值为 32,即 100000。当编译器在解释一个方程并产生字节码时,遇到了yield关键字后解释器就会将100000加至函数的__code__.co_flags标识中,然后通过比特运算获取flags中包含的种种信息。

      >>> def func(num):
      ...     x = yield num
      ...     y = yield x
      ...     return y
      ...
      >>> print(func.__code__.co_flags & 32)
      32
      >>>
  • co_firstlineno:代码块对应文件的起始行。

      >>> def func(a,b,c,*,d,e):
      ...     pass
      ...
      >>> print(func.__code__.co_firstlineno)
      1
      >>>
  • *co_code:编译得到的字节码指令序列。

      >>> def func(num):
      ...     print(num)
      ...
      >>> print(func.__code__.co_code)
      b't\x00|\x00\x83\x01\x01\x00d\x00S\x00'
      >>>
  • *co_consts:常量池,一个元组对象,保存代码块中的所有常量。

      >>> def func(a,b,c):
      ...     x = 1
      ...     f = False
      ...     t = True
      ...
      >>> print(func.__code__.co_consts)
      (None, 1, False, True)
      >>>
  • *co_names:一个元组,保存代码块中不在当前作用域的变量。

      >>> d = 1
      >>> def func(a,b,c):
      ...     print(a,b,c,d)
      ...
      >>> print(func.__code__.co_names)
      ('print', 'd')
      >>>
  • *co_varnames:一个元组,保存在当前作用域中的变量。

      >>> d = 1
      >>> def func(a,b,c):
      ...     print(a,b,c,d)
      ...
      >>> print(func.__code__.co_varnames)
      ('a', 'b', 'c')
      >>>
  • *co_freevars:内层函数引用的外层函数的作用域中的变量,即块内使用,但是并未在该块内定义的变量(不含全局变量、内置变量)。

      >>> def outer():
      ...     e = 'enclose'
      ...     def inner():
      ...         print(e)
      ...     return inner
      ...
      >>> print(outer().__code__.co_freevars)
      ('e',)
      >>>
  • *co_cellvars:是与co_freevars相对的概念,指被内部嵌套块(函数)引用的变量组成的元组。

      >>> def outer():
      ...     e = 'enclose'
      ...     def inner():
      ...         print(e)
      ...     return inner
      ...
      >>> print(outer.__code__.co_cellvars)
      ('e',)
      >>

    magic number

    pyc文件主要包括三个部分,首先是magic number,其次是timestamp,然后是PyCodeObject。magic number 是Python定义的一个整数值,不同版本的Python会定义不同的magic number,这个值是为了保证Python能够加载正确的pyc。

    typedef struct {
      unsigned short min;
      unsigned short max;
      wchar_t version[MAX_VERSION_SIZE];
    } PYC_MAGIC;
    
    static PYC_MAGIC magic_values[] = {
      { 50823, 50823, L"2.0" },
      { 60202, 60202, L"2.1" },
      { 60717, 60717, L"2.2" },
      { 62011, 62021, L"2.3" },
      { 62041, 62061, L"2.4" },
      { 62071, 62131, L"2.5" },
      { 62151, 62161, L"2.6" },
      { 62171, 62211, L"2.7" },
      { 3000, 3131, L"3.0" },
      { 3141, 3151, L"3.1" },
      { 3160, 3180, L"3.2" },
      { 3190, 3230, L"3.3" },
      { 3250, 3310, L"3.4" },
      { 3320, 3351, L"3.5" },
      { 3360, 3379, L"3.6" },
      { 3390, 3399, L"3.7" },
      { 3400, 3419, L"3.8" },
      { 3420, 3429, L"3.9" },
      { 3430, 3439, L"3.10" },
      { 0 }
    };

pyc文件

magic number 和 timestamp 的写入

根据调用关系,最终python调用此函数写入整型数据。可以看出python是一个字节一个字节的向文件写入数据的。

typedef struct {
    FILE *fp;
    int error;  /* see WFERR_* values */
    int depth;
    PyObject *str;
    char *ptr;
    const char *end;
    char *buf;
    _Py_hashtable_t *hashtable;
    int version;
} WFILE;

#define w_byte(c, p) do {                               \
        if ((p)->ptr != (p)->end || w_reserve((p), 1))  \
            *(p)->ptr++ = (c);                          \
    } while(0)

static void
w_long(long x, WFILE *p)
{
    w_byte((char)( x      & 0xff), p);
    w_byte((char)((x>> 8) & 0xff), p);
    w_byte((char)((x>>16) & 0xff), p);
    w_byte((char)((x>>24) & 0xff), p);
}
  • WFILE:对一个文件句柄进行了包装。

    • PyObject *str:在写入文件时为一个dict对象,在读取文件时为一个list对象(为了处理intern的字符串)

w_object写入

根据调用关系,对PyCodeObject的写入最终调用w_object函数。

static void
w_object(PyObject *v, WFILE *p)
{
    char flag = '\0';
    p->depth++;
    if (p->depth > MAX_MARSHAL_STACK_DEPTH) {
        p->error = WFERR_NESTEDTOODEEP;
    }
    else if (v == NULL) {
        w_byte(TYPE_NULL, p);
    }
    else if (v == Py_None) {
        w_byte(TYPE_NONE, p);
    }
    else if (v == PyExc_StopIteration) {
        w_byte(TYPE_STOPITER, p);
    }
    else if (v == Py_Ellipsis) {
        w_byte(TYPE_ELLIPSIS, p);
    }
    else if (v == Py_False) {
        w_byte(TYPE_FALSE, p);
    }
    else if (v == Py_True) {
        w_byte(TYPE_TRUE, p);
    }
    else if (!w_ref(v, &flag, p))
        w_complex_object(v, flag, p);

    p->depth--;
}
  • 通过条件判断语句判断对象类型,调用不同的方法进行写入。
  • w_complex_object,内部也是不断判断对象类型,然后写入,最终都调用的是w_byte,如果是列表、元组之类的数据,则会通过循环,递归w_object函数写入数据,例如PyDict对象,对遍历出来的每一个键,值都执行w_object函数。

      static void
      w_complex_object(PyObject *v, char flag, WFILE *p)
      {
          Py_ssize_t i, n;
          if (PyLong_CheckExact(v)) {
              ...
          }
          else if (PyFloat_CheckExact(v)) {
              ...
          }
          else if (PyComplex_CheckExact(v)) {
              ...
          }
          else if (PyBytes_CheckExact(v)) {
              ...
          }
          else if (PyUnicode_CheckExact(v)) {
              ...
          }
          else if (PyTuple_CheckExact(v)) {
              ...
          }
          else if (PyList_CheckExact(v)) {
              ...
          }
          else if (PyDict_CheckExact(v)) {
              Py_ssize_t pos;
              PyObject *key, *value;
              W_TYPE(TYPE_DICT, p);
              /* This one is NULL object terminated! */
              pos = 0;
              while (PyDict_Next(v, &pos, &key, &value)) {
                  w_object(key, p);
                  w_object(value, p);
              }
              w_object((PyObject *)NULL, p);
          }
          else if (PyAnySet_CheckExact(v)) {
             ...
          }
          else if (PyCode_Check(v)) {
              PyCodeObject *co = (PyCodeObject *)v;
              W_TYPE(TYPE_CODE, p);
              w_long(co->co_argcount, p);
              w_long(co->co_posonlyargcount, p);
              w_long(co->co_kwonlyargcount, p);
              w_long(co->co_nlocals, p);
              w_long(co->co_stacksize, p);
              w_long(co->co_flags, p);
              w_object(co->co_code, p);
              w_object(co->co_consts, p);
              w_object(co->co_names, p);
              w_object(co->co_varnames, p);
              w_object(co->co_freevars, p);
              w_object(co->co_cellvars, p);
              w_object(co->co_filename, p);
              w_object(co->co_name, p);
              w_long(co->co_firstlineno, p);
              w_object(co->co_linetable, p);
          }
          else if (PyObject_CheckBuffer(v)) {
              ...
          }
          else {
              W_TYPE(TYPE_UNKNOWN, p);
              p->error = WFERR_UNMARSHALLABLE;
          }
      }
    • PyCode_Check(v),可以看到如果对象是一个PyCodeObject,则就直接写入,也就是说,如果对象包括很多代码块,那么在最大的那个代码块中一定包含了其余小的代码块,在写pyc文件时,小的代码块将被递归写入文件,也就是说,pyc文件是一个有组织结构的文件。
    • 注意到,TYPE_CODE等类型信息,如果没有类型信息,那么当python再次加载pyc文件的时候,就没办法知道字节流中隐藏的结构和蕴含的信息,所以Python必须往pyc文件写入一个标识。如果Python在pyc中发现了这样的标识,则预示着上一个对象结束,新的对象开始,并且也知道新对象是什么样的对象,从而也知道该执行什么样的加载动作。

          #define TYPE_NULL               '0'
          #define TYPE_NONE               'N'
          #define TYPE_FALSE              'F'
          #define TYPE_TRUE               'T'
          #define TYPE_STOPITER           'S'
          #define TYPE_ELLIPSIS           '.'
          #define TYPE_INT                'i'
          /* TYPE_INT64 is not generated anymore.
          Supported for backward compatibility only. */
          #define TYPE_INT64              'I'
          #define TYPE_FLOAT              'f'
          #define TYPE_BINARY_FLOAT       'g'
          #define TYPE_COMPLEX            'x'
          #define TYPE_BINARY_COMPLEX     'y'
          #define TYPE_LONG               'l'
          #define TYPE_STRING             's'
          #define TYPE_INTERNED           't'
          #define TYPE_REF                'r'
          #define TYPE_TUPLE              '('
          #define TYPE_LIST               '['
          #define TYPE_DICT               '{'
          #define TYPE_CODE               'c'
          #define TYPE_UNICODE            'u'
          #define TYPE_UNKNOWN            '?'
          #define TYPE_SET                '<'
          #define TYPE_FROZENSET          '>'
          #define FLAG_REF                '\x80' /* with a type, add obj to index */
          #define TYPE_ASCII              'a'
          #define TYPE_ASCII_INTERNED     'A'
          #define TYPE_SMALL_TUPLE        ')'
          #define TYPE_SHORT_ASCII        'z'
          #define TYPE_SHORT_ASCII_INTERNED 'Z'

声明:Hello World|版权所有,违者必究|如未注明,均为原创|本网站采用BY-NC-SA协议进行授权

转载:转载请注明原文链接 - Python PyCodeObject 字节码对象


我的朋友,理论是灰色的,而生活之树是常青的!